diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 4837aec030e3..8c45fb977e3f 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -24,37 +24,22 @@ void build_constraints(Builder& builder, if (collect_gates_per_opcode) { constraint_system.gates_per_opcode.resize(constraint_system.num_acir_opcodes, 0); } - size_t prev_gate_count = 0; - auto compute_gate_diff = [&]() -> size_t { - if (!collect_gates_per_opcode) { - return 0; - } - size_t new_gate_count = builder.get_num_gates(); - size_t diff = new_gate_count - prev_gate_count; - prev_gate_count = new_gate_count; - return diff; - }; - - auto track_gate_diff = [&](std::vector& gates_per_opcode, size_t opcode_index) -> void { - if (collect_gates_per_opcode) { - gates_per_opcode[opcode_index] = compute_gate_diff(); - } - }; + GateCounter gate_counter{ &builder, collect_gates_per_opcode }; // Add arithmetic gates for (size_t i = 0; i < constraint_system.poly_triple_constraints.size(); ++i) { const auto& constraint = constraint_system.poly_triple_constraints.at(i); builder.create_poly_gate(constraint); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.poly_triple_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.poly_triple_constraints.at(i)); } for (size_t i = 0; i < constraint_system.quad_constraints.size(); ++i) { const auto& constraint = constraint_system.quad_constraints.at(i); builder.create_big_mul_gate(constraint); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.quad_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.quad_constraints.at(i)); } // Add logic constraint @@ -62,132 +47,132 @@ void build_constraints(Builder& builder, const auto& constraint = constraint_system.logic_constraints.at(i); create_logic_gate( builder, constraint.a, constraint.b, constraint.result, constraint.num_bits, constraint.is_xor_gate); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.logic_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.logic_constraints.at(i)); } // Add range constraint for (size_t i = 0; i < constraint_system.range_constraints.size(); ++i) { const auto& constraint = constraint_system.range_constraints.at(i); builder.create_range_constraint(constraint.witness, constraint.num_bits, ""); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.range_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.range_constraints.at(i)); } // Add aes128 constraints for (size_t i = 0; i < constraint_system.aes128_constraints.size(); ++i) { const auto& constraint = constraint_system.aes128_constraints.at(i); create_aes128_constraints(builder, constraint); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.aes128_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.aes128_constraints.at(i)); } // Add sha256 constraints for (size_t i = 0; i < constraint_system.sha256_constraints.size(); ++i) { const auto& constraint = constraint_system.sha256_constraints.at(i); create_sha256_constraints(builder, constraint); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.sha256_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.sha256_constraints.at(i)); } for (size_t i = 0; i < constraint_system.sha256_compression.size(); ++i) { const auto& constraint = constraint_system.sha256_compression[i]; create_sha256_compression_constraints(builder, constraint); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.sha256_compression[i]); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.sha256_compression[i]); } // Add schnorr constraints for (size_t i = 0; i < constraint_system.schnorr_constraints.size(); ++i) { const auto& constraint = constraint_system.schnorr_constraints.at(i); create_schnorr_verify_constraints(builder, constraint); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.schnorr_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.schnorr_constraints.at(i)); } // Add ECDSA k1 constraints for (size_t i = 0; i < constraint_system.ecdsa_k1_constraints.size(); ++i) { const auto& constraint = constraint_system.ecdsa_k1_constraints.at(i); create_ecdsa_k1_verify_constraints(builder, constraint, has_valid_witness_assignments); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.ecdsa_k1_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.ecdsa_k1_constraints.at(i)); } // Add ECDSA r1 constraints for (size_t i = 0; i < constraint_system.ecdsa_r1_constraints.size(); ++i) { const auto& constraint = constraint_system.ecdsa_r1_constraints.at(i); create_ecdsa_r1_verify_constraints(builder, constraint, has_valid_witness_assignments); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.ecdsa_r1_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.ecdsa_r1_constraints.at(i)); } // Add blake2s constraints for (size_t i = 0; i < constraint_system.blake2s_constraints.size(); ++i) { const auto& constraint = constraint_system.blake2s_constraints.at(i); create_blake2s_constraints(builder, constraint); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.blake2s_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.blake2s_constraints.at(i)); } // Add blake3 constraints for (size_t i = 0; i < constraint_system.blake3_constraints.size(); ++i) { const auto& constraint = constraint_system.blake3_constraints.at(i); create_blake3_constraints(builder, constraint); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.blake3_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.blake3_constraints.at(i)); } // Add keccak constraints for (size_t i = 0; i < constraint_system.keccak_constraints.size(); ++i) { const auto& constraint = constraint_system.keccak_constraints.at(i); create_keccak_constraints(builder, constraint); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.keccak_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.keccak_constraints.at(i)); } for (size_t i = 0; i < constraint_system.keccak_permutations.size(); ++i) { const auto& constraint = constraint_system.keccak_permutations[i]; create_keccak_permutations(builder, constraint); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.keccak_permutations[i]); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.keccak_permutations[i]); } // Add pedersen constraints for (size_t i = 0; i < constraint_system.pedersen_constraints.size(); ++i) { const auto& constraint = constraint_system.pedersen_constraints.at(i); create_pedersen_constraint(builder, constraint); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.pedersen_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.pedersen_constraints.at(i)); } for (size_t i = 0; i < constraint_system.pedersen_hash_constraints.size(); ++i) { const auto& constraint = constraint_system.pedersen_hash_constraints.at(i); create_pedersen_hash_constraint(builder, constraint); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.pedersen_hash_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.pedersen_hash_constraints.at(i)); } for (size_t i = 0; i < constraint_system.poseidon2_constraints.size(); ++i) { const auto& constraint = constraint_system.poseidon2_constraints.at(i); create_poseidon2_permutations(builder, constraint); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.poseidon2_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.poseidon2_constraints.at(i)); } // Add multi scalar mul constraints for (size_t i = 0; i < constraint_system.multi_scalar_mul_constraints.size(); ++i) { const auto& constraint = constraint_system.multi_scalar_mul_constraints.at(i); create_multi_scalar_mul_constraint(builder, constraint, has_valid_witness_assignments); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.multi_scalar_mul_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.multi_scalar_mul_constraints.at(i)); } // Add ec add constraints for (size_t i = 0; i < constraint_system.ec_add_constraints.size(); ++i) { const auto& constraint = constraint_system.ec_add_constraints.at(i); create_ec_add_constraint(builder, constraint, has_valid_witness_assignments); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.ec_add_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.ec_add_constraints.at(i)); } // Add block constraints @@ -196,7 +181,7 @@ void build_constraints(Builder& builder, create_block_constraints(builder, constraint, has_valid_witness_assignments); if (collect_gates_per_opcode) { size_t avg_gates_per_opcode = - compute_gate_diff() / constraint_system.original_opcode_indices.block_constraints.at(i).size(); + gate_counter.compute_diff() / constraint_system.original_opcode_indices.block_constraints.at(i).size(); for (size_t opcode_index : constraint_system.original_opcode_indices.block_constraints.at(i)) { constraint_system.gates_per_opcode[opcode_index] = avg_gates_per_opcode; } @@ -209,171 +194,183 @@ void build_constraints(Builder& builder, for (size_t i = 0; i < constraint_system.bigint_from_le_bytes_constraints.size(); ++i) { const auto& constraint = constraint_system.bigint_from_le_bytes_constraints.at(i); create_bigint_from_le_bytes_constraint(builder, constraint, dsl_bigints); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.bigint_from_le_bytes_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.bigint_from_le_bytes_constraints.at(i)); } for (size_t i = 0; i < constraint_system.bigint_operations.size(); ++i) { const auto& constraint = constraint_system.bigint_operations[i]; create_bigint_operations_constraint(constraint, dsl_bigints, has_valid_witness_assignments); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.bigint_operations[i]); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.bigint_operations[i]); } for (size_t i = 0; i < constraint_system.bigint_to_le_bytes_constraints.size(); ++i) { const auto& constraint = constraint_system.bigint_to_le_bytes_constraints.at(i); create_bigint_to_le_bytes_constraint(builder, constraint, dsl_bigints); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.bigint_to_le_bytes_constraints.at(i)); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.bigint_to_le_bytes_constraints.at(i)); } - // RecursionConstraint + // RecursionConstraints // TODO(https://github.com/AztecProtocol/barretenberg/issues/817): disable these for MegaHonk for now since we're // not yet dealing with proper recursion if constexpr (IsMegaBuilder) { if (!constraint_system.recursion_constraints.empty()) { - info("WARNING: this circuit contains recursion_constraints!"); + info("WARNING: this circuit contains unhandled recursion_constraints!"); } - } else { - // These are set and modified whenever we encounter a recursion opcode - // - // These should not be set by the caller - // TODO(maxim): Check if this is always the case. ie I won't receive a proof that will set the first - // TODO(maxim): input_aggregation_object to be non-zero. - // TODO(maxim): if not, we can add input_aggregation_object to the proof too for all recursive proofs - // TODO(maxim): This might be the case for proof trees where the proofs are created on different machines - AggregationObjectIndices current_input_aggregation_object = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - AggregationObjectIndices current_output_aggregation_object = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - - // Get the size of proof with no public inputs prepended to it - // This is used while processing recursion constraints to determine whether - // the proof we are verifying contains a recursive proof itself - auto proof_size_no_pub_inputs = recursion_proof_size_without_public_inputs(); - - // Add recursion constraints - for (size_t constraint_idx = 0; constraint_idx < constraint_system.recursion_constraints.size(); - ++constraint_idx) { - auto constraint = constraint_system.recursion_constraints[constraint_idx]; - // A proof passed into the constraint should be stripped of its public inputs, except in the case where a - // proof contains an aggregation object itself. We refer to this as the `nested_aggregation_object`. The - // verifier circuit requires that the indices to a nested proof aggregation state are a circuit constant. - // The user tells us they how they want these constants set by keeping the nested aggregation object - // attached to the proof as public inputs. As this is the only object that can prepended to the proof if the - // proof is above the expected size (with public inputs stripped) - AggregationObjectPubInputIndices nested_aggregation_object = {}; - // If the proof has public inputs attached to it, we should handle setting the nested aggregation object - if (constraint.proof.size() > proof_size_no_pub_inputs) { - // The public inputs attached to a proof should match the aggregation object in size - if (constraint.proof.size() - proof_size_no_pub_inputs != bb::AGGREGATION_OBJECT_SIZE) { - auto error_string = format( - "Public inputs are always stripped from proofs unless we have a recursive proof.\n" - "Thus, public inputs attached to a proof must match the recursive aggregation object in size " - "which is ", - bb::AGGREGATION_OBJECT_SIZE); - throw_or_abort(error_string); - } - for (size_t i = 0; i < bb::AGGREGATION_OBJECT_SIZE; ++i) { - // Set the nested aggregation object indices to the current size of the public inputs - // This way we know that the nested aggregation object indices will always be the last - // indices of the public inputs - nested_aggregation_object[i] = static_cast(constraint.public_inputs.size()); - // Attach the nested aggregation object to the end of the public inputs to fill in - // the slot where the nested aggregation object index will point into - constraint.public_inputs.emplace_back(constraint.proof[i]); - } - // Remove the aggregation object so that they can be handled as normal public inputs - // in they way taht the recursion constraint expects - constraint.proof.erase(constraint.proof.begin(), - constraint.proof.begin() + - static_cast(bb::AGGREGATION_OBJECT_SIZE)); - } - - current_output_aggregation_object = create_recursion_constraints(builder, - constraint, - current_input_aggregation_object, - nested_aggregation_object, - has_valid_witness_assignments); - current_input_aggregation_object = current_output_aggregation_object; - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.recursion_constraints[constraint_idx]); + if (!constraint_system.honk_recursion_constraints.empty()) { + info("WARNING: this circuit contains unhandled honk_recursion_constraints!"); } + } else { + process_plonk_recursion_constraints(builder, constraint_system, has_valid_witness_assignments, gate_counter); + process_honk_recursion_constraints(builder, constraint_system, has_valid_witness_assignments, gate_counter); - // Now that the circuit has been completely built, we add the output aggregation as public - // inputs. - if (!constraint_system.recursion_constraints.empty()) { - - // First add the output aggregation object as public inputs - // Set the indices as public inputs because they are no longer being - // created in ACIR - for (const auto& idx : current_output_aggregation_object) { - builder.set_public_input(idx); - } - + // If the circuit does not itself contain honk recursion constraints but is going to be proven with honk then + // recursively verified, add a default aggregation object + if (constraint_system.honk_recursion_constraints.empty() && honk_recursion && + builder.is_recursive_circuit) { // Set a default aggregation object if we don't have one. + AggregationObjectIndices current_aggregation_object = + stdlib::recursion::init_default_agg_obj_indices(builder); // Make sure the verification key records the public input indices of the // final recursion output. - builder.set_recursive_proof(current_output_aggregation_object); + builder.add_recursive_proof(current_aggregation_object); } } +} - // HonkRecursionConstraint - // TODO(https://github.com/AztecProtocol/barretenberg/issues/817): disable these for MegaHonk for now since we're - // not yet dealing with proper recursion - if constexpr (IsMegaBuilder) { - if (!constraint_system.honk_recursion_constraints.empty()) { - info("WARNING: this circuit contains honk_recursion_constraints!"); - } - } else { - AggregationObjectIndices current_aggregation_object = - stdlib::recursion::init_default_agg_obj_indices(builder); - - // Add recursion constraints - for (size_t i = 0; i < constraint_system.honk_recursion_constraints.size(); ++i) { - auto& constraint = constraint_system.honk_recursion_constraints.at(i); - // A proof passed into the constraint should be stripped of its inner public inputs, but not the nested - // aggregation object itself. The verifier circuit requires that the indices to a nested proof aggregation - // state are a circuit constant. The user tells us they how they want these constants set by keeping the - // nested aggregation object attached to the proof as public inputs. +void process_plonk_recursion_constraints(Builder& builder, + AcirFormat& constraint_system, + bool has_valid_witness_assignments, + GateCounter& gate_counter) +{ + + // These are set and modified whenever we encounter a recursion opcode + // + // These should not be set by the caller + // TODO(maxim): Check if this is always the case. ie I won't receive a proof that will set the first + // TODO(maxim): input_aggregation_object to be non-zero. + // TODO(maxim): if not, we can add input_aggregation_object to the proof too for all recursive proofs + // TODO(maxim): This might be the case for proof trees where the proofs are created on different machines + AggregationObjectIndices current_input_aggregation_object = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + AggregationObjectIndices current_output_aggregation_object = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + // Get the size of proof with no public inputs prepended to it + // This is used while processing recursion constraints to determine whether + // the proof we are verifying contains a recursive proof itself + auto proof_size_no_pub_inputs = recursion_proof_size_without_public_inputs(); + + // Add recursion constraints + for (size_t constraint_idx = 0; constraint_idx < constraint_system.recursion_constraints.size(); ++constraint_idx) { + auto constraint = constraint_system.recursion_constraints[constraint_idx]; + + // A proof passed into the constraint should be stripped of its public inputs, except in the case where a + // proof contains an aggregation object itself. We refer to this as the `nested_aggregation_object`. The + // verifier circuit requires that the indices to a nested proof aggregation state are a circuit constant. + // The user tells us they how they want these constants set by keeping the nested aggregation object + // attached to the proof as public inputs. As this is the only object that can prepended to the proof if the + // proof is above the expected size (with public inputs stripped) + AggregationObjectPubInputIndices nested_aggregation_object = {}; + // If the proof has public inputs attached to it, we should handle setting the nested aggregation object + if (constraint.proof.size() > proof_size_no_pub_inputs) { + // The public inputs attached to a proof should match the aggregation object in size + if (constraint.proof.size() - proof_size_no_pub_inputs != bb::AGGREGATION_OBJECT_SIZE) { + auto error_string = format( + "Public inputs are always stripped from proofs unless we have a recursive proof.\n" + "Thus, public inputs attached to a proof must match the recursive aggregation object in size " + "which is ", + bb::AGGREGATION_OBJECT_SIZE); + throw_or_abort(error_string); + } for (size_t i = 0; i < bb::AGGREGATION_OBJECT_SIZE; ++i) { - // Adding the nested aggregation object to the constraint's public inputs - constraint.public_inputs.emplace_back( - constraint.proof[HonkRecursionConstraint::inner_public_input_offset + i]); + // Set the nested aggregation object indices to the current size of the public inputs + // This way we know that the nested aggregation object indices will always be the last + // indices of the public inputs + nested_aggregation_object[i] = static_cast(constraint.public_inputs.size()); + // Attach the nested aggregation object to the end of the public inputs to fill in + // the slot where the nested aggregation object index will point into + constraint.public_inputs.emplace_back(constraint.proof[i]); } // Remove the aggregation object so that they can be handled as normal public inputs - // in they way that the recursion constraint expects - constraint.proof.erase(constraint.proof.begin() + HonkRecursionConstraint::inner_public_input_offset, - constraint.proof.begin() + - static_cast(HonkRecursionConstraint::inner_public_input_offset + - bb::AGGREGATION_OBJECT_SIZE)); - current_aggregation_object = create_honk_recursion_constraints( - builder, constraint, current_aggregation_object, has_valid_witness_assignments); - track_gate_diff(constraint_system.gates_per_opcode, - constraint_system.original_opcode_indices.honk_recursion_constraints.at(i)); + // in the way that the recursion constraint expects + constraint.proof.erase(constraint.proof.begin(), + constraint.proof.begin() + static_cast(bb::AGGREGATION_OBJECT_SIZE)); } - // Now that the circuit has been completely built, we add the output aggregation as public - // inputs. - if (!constraint_system.honk_recursion_constraints.empty()) { + current_output_aggregation_object = create_recursion_constraints(builder, + constraint, + current_input_aggregation_object, + nested_aggregation_object, + has_valid_witness_assignments); + current_input_aggregation_object = current_output_aggregation_object; + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.recursion_constraints[constraint_idx]); + } - // First add the output aggregation object as public inputs - // Set the indices as public inputs because they are no longer being - // created in ACIR - for (const auto& idx : current_aggregation_object) { - builder.set_public_input(idx); - } + // Now that the circuit has been completely built, we add the output aggregation as public + // inputs. + if (!constraint_system.recursion_constraints.empty()) { - // Make sure the verification key records the public input indices of the - // final recursion output. - builder.set_recursive_proof(current_aggregation_object); - } else if (honk_recursion && - builder.is_recursive_circuit) { // Set a default aggregation object if we don't have one. - AggregationObjectIndices current_aggregation_object = - stdlib::recursion::init_default_agg_obj_indices(builder); - // Make sure the verification key records the public input indices of the - // final recursion output. - builder.add_recursive_proof(current_aggregation_object); + // First add the output aggregation object as public inputs + // Set the indices as public inputs because they are no longer being + // created in ACIR + for (const auto& idx : current_output_aggregation_object) { + builder.set_public_input(idx); } + + // Make sure the verification key records the public input indices of the + // final recursion output. + builder.set_recursive_proof(current_output_aggregation_object); } -} +}; + +void process_honk_recursion_constraints(Builder& builder, + AcirFormat& constraint_system, + bool has_valid_witness_assignments, + GateCounter& gate_counter) +{ + AggregationObjectIndices current_aggregation_object = + stdlib::recursion::init_default_agg_obj_indices(builder); + + // Add recursion constraints + for (size_t i = 0; i < constraint_system.honk_recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.honk_recursion_constraints.at(i); + // A proof passed into the constraint should be stripped of its inner public inputs, but not the nested + // aggregation object itself. The verifier circuit requires that the indices to a nested proof aggregation + // state are a circuit constant. The user tells us they how they want these constants set by keeping the + // nested aggregation object attached to the proof as public inputs. + for (size_t i = 0; i < bb::AGGREGATION_OBJECT_SIZE; ++i) { + // Adding the nested aggregation object to the constraint's public inputs + constraint.public_inputs.emplace_back(constraint.proof[HONK_RECURSION_PUBLIC_INPUT_OFFSET + i]); + } + // Remove the aggregation object so that they can be handled as normal public inputs + // in they way that the recursion constraint expects + constraint.proof.erase( + constraint.proof.begin() + HONK_RECURSION_PUBLIC_INPUT_OFFSET, + constraint.proof.begin() + + static_cast(HONK_RECURSION_PUBLIC_INPUT_OFFSET + bb::AGGREGATION_OBJECT_SIZE)); + current_aggregation_object = create_honk_recursion_constraints( + builder, constraint, current_aggregation_object, has_valid_witness_assignments); + gate_counter.track_diff(constraint_system.gates_per_opcode, + constraint_system.original_opcode_indices.honk_recursion_constraints.at(i)); + } + + // Now that the circuit has been completely built, we add the output aggregation as public + // inputs. + if (!constraint_system.honk_recursion_constraints.empty()) { + + // First add the output aggregation object as public inputs + // Set the indices as public inputs because they are no longer being + // created in ACIR + for (const auto& idx : current_aggregation_object) { + builder.set_public_input(idx); + } + + // Make sure the verification key records the public input indices of the + // final recursion output. + builder.set_recursive_proof(current_aggregation_object); + } +}; /** * @brief Specialization for creating Ultra circuit from acir constraints and optionally a witness diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index 1b0e8b5057b8..81773ed655f2 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -94,7 +94,7 @@ struct AcirFormat { std::vector multi_scalar_mul_constraints; std::vector ec_add_constraints; std::vector recursion_constraints; - std::vector honk_recursion_constraints; + std::vector honk_recursion_constraints; std::vector bigint_from_le_bytes_constraints; std::vector bigint_to_le_bytes_constraints; std::vector bigint_operations; @@ -204,4 +204,49 @@ void build_constraints( // circuit. This distinction is needed to not add the default // aggregation object when we're not using the honk RV. +/** + * @brief Utility class for tracking the gate count of acir constraints + * + */ +template class GateCounter { + public: + GateCounter(Builder* builder, bool collect_gates_per_opcode) + : builder(builder) + , collect_gates_per_opcode(collect_gates_per_opcode) + {} + + size_t compute_diff() + { + if (!collect_gates_per_opcode) { + return 0; + } + size_t new_gate_count = builder->get_num_gates(); + size_t diff = new_gate_count - prev_gate_count; + prev_gate_count = new_gate_count; + return diff; + } + + void track_diff(std::vector& gates_per_opcode, size_t opcode_index) + { + if (collect_gates_per_opcode) { + gates_per_opcode[opcode_index] = compute_diff(); + } + } + + private: + Builder* builder; + bool collect_gates_per_opcode; + size_t prev_gate_count{}; +}; + +void process_plonk_recursion_constraints(Builder& builder, + AcirFormat& constraint_system, + bool has_valid_witness_assignments, + GateCounter& gate_counter); +void process_honk_recursion_constraints(Builder& builder, + AcirFormat& constraint_system, + bool has_valid_witness_assignments, + bool honk_recursion, + GateCounter& gate_counter); + } // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_integration.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_integration.test.cpp index 387c11e7609d..e0353157aabe 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_integration.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_integration.test.cpp @@ -543,4 +543,26 @@ TEST_F(AcirIntegrationTest, DISABLED_UpdateAcirCircuit) EXPECT_TRUE(prove_and_verify_honk(circuit)); } +/** + * @brief Test recursive honk recursive verification + * + */ +TEST_F(AcirIntegrationTest, DISABLED_HonkRecursion) +{ + using Flavor = UltraFlavor; + using Builder = Flavor::CircuitBuilder; + + std::string test_name = "verify_honk_proof"; // arbitrary program with RAM gates + // Note: honk_recursion set to false here because the selection of the honk recursive verifier is indicated by the + // proof_type field of the constraint generated from noir. + auto acir_program = get_program_data_from_test_file(test_name, + /*honk_recursion=*/false); + + // Construct a bberg circuit from the acir representation + auto circuit = acir_format::create_circuit(acir_program.constraints, 0, acir_program.witness); + + EXPECT_TRUE(CircuitChecker::check(circuit)); + EXPECT_TRUE(prove_and_verify_honk(circuit)); +} + #endif diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp index 7170c7a187de..5fedd5ac9683 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp @@ -415,27 +415,34 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, }); af.original_opcode_indices.keccak_permutations.push_back(opcode_index); } else if constexpr (std::is_same_v) { - if (honk_recursion) { // if we're using the honk recursive verifier - auto c = HonkRecursionConstraint{ - .key = map(arg.verification_key, [](auto& e) { return get_witness_from_function_input(e); }), - .proof = map(arg.proof, [](auto& e) { return get_witness_from_function_input(e); }), - .public_inputs = - map(arg.public_inputs, [](auto& e) { return get_witness_from_function_input(e); }), - }; + + auto input_key = get_witness_from_function_input(arg.key_hash); + + auto proof_type_in = arg.proof_type; + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1074): Eventually arg.proof_type will be + // the only means for setting the proof type. use of honk_recursion flag in this context can go away + // once all noir programs (e.g. protocol circuits) are updated to use the new pattern. + if (honk_recursion && proof_type_in != HONK_RECURSION) { + proof_type_in = HONK_RECURSION; + } + + auto c = RecursionConstraint{ + .key = map(arg.verification_key, [](auto& e) { return get_witness_from_function_input(e); }), + .proof = map(arg.proof, [](auto& e) { return get_witness_from_function_input(e); }), + .public_inputs = map(arg.public_inputs, [](auto& e) { return get_witness_from_function_input(e); }), + .key_hash = input_key, + .proof_type = proof_type_in, + }; + // Add the recursion constraint to the appropriate container based on proof type + if (c.proof_type == PLONK_RECURSION) { + af.recursion_constraints.push_back(c); + af.original_opcode_indices.recursion_constraints.push_back(opcode_index); + } else if (c.proof_type == HONK_RECURSION) { af.honk_recursion_constraints.push_back(c); af.original_opcode_indices.honk_recursion_constraints.push_back(opcode_index); } else { - auto input_key = get_witness_from_function_input(arg.key_hash); - - auto c = RecursionConstraint{ - .key = map(arg.verification_key, [](auto& e) { return get_witness_from_function_input(e); }), - .proof = map(arg.proof, [](auto& e) { return get_witness_from_function_input(e); }), - .public_inputs = - map(arg.public_inputs, [](auto& e) { return get_witness_from_function_input(e); }), - .key_hash = input_key, - }; - af.recursion_constraints.push_back(c); - af.original_opcode_indices.recursion_constraints.push_back(opcode_index); + info("Invalid PROOF_TYPE in RecursionConstraint!"); + ASSERT(false); } } else if constexpr (std::is_same_v) { af.bigint_from_le_bytes_constraints.push_back(BigIntFromLeBytes{ diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp index da877ebb2d20..317a295e7c25 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp @@ -27,7 +27,7 @@ using aggregation_state_ct = bb::stdlib::recursion::aggregation_state; * @param proof_fields */ void create_dummy_vkey_and_proof(Builder& builder, - const HonkRecursionConstraint& input, + const RecursionConstraint& input, std::vector& key_fields, std::vector& proof_fields) { @@ -36,16 +36,15 @@ void create_dummy_vkey_and_proof(Builder& builder, // Set vkey->circuit_size correctly based on the proof size size_t num_frs_comm = bb::field_conversion::calc_num_bn254_frs(); size_t num_frs_fr = bb::field_conversion::calc_num_bn254_frs(); - assert((input.proof.size() - HonkRecursionConstraint::inner_public_input_offset - - UltraFlavor::NUM_WITNESS_ENTITIES * num_frs_comm - UltraFlavor::NUM_ALL_ENTITIES * num_frs_fr - - 2 * num_frs_comm) % + assert((input.proof.size() - HONK_RECURSION_PUBLIC_INPUT_OFFSET - UltraFlavor::NUM_WITNESS_ENTITIES * num_frs_comm - + UltraFlavor::NUM_ALL_ENTITIES * num_frs_fr - 2 * num_frs_comm) % (num_frs_comm + num_frs_fr * UltraFlavor::BATCHED_RELATION_PARTIAL_LENGTH) == 0); // Note: this computation should always result in log_circuit_size = CONST_PROOF_SIZE_LOG_N - auto log_circuit_size = (input.proof.size() - HonkRecursionConstraint::inner_public_input_offset - - UltraFlavor::NUM_WITNESS_ENTITIES * num_frs_comm - - UltraFlavor::NUM_ALL_ENTITIES * num_frs_fr - 2 * num_frs_comm) / - (num_frs_comm + num_frs_fr * UltraFlavor::BATCHED_RELATION_PARTIAL_LENGTH); + auto log_circuit_size = + (input.proof.size() - HONK_RECURSION_PUBLIC_INPUT_OFFSET - UltraFlavor::NUM_WITNESS_ENTITIES * num_frs_comm - + UltraFlavor::NUM_ALL_ENTITIES * num_frs_fr - 2 * num_frs_comm) / + (num_frs_comm + num_frs_fr * UltraFlavor::BATCHED_RELATION_PARTIAL_LENGTH); // First key field is circuit size builder.assert_equal(builder.add_variable(1 << log_circuit_size), key_fields[0].witness_index); // Second key field is number of public inputs @@ -73,7 +72,7 @@ void create_dummy_vkey_and_proof(Builder& builder, offset += 4; } - offset = HonkRecursionConstraint::inner_public_input_offset; + offset = HONK_RECURSION_PUBLIC_INPUT_OFFSET; // first 3 things builder.assert_equal(builder.add_variable(1 << log_circuit_size), proof_fields[0].witness_index); builder.assert_equal(builder.add_variable(input.public_inputs.size()), proof_fields[1].witness_index); @@ -151,7 +150,7 @@ void create_dummy_vkey_and_proof(Builder& builder, * or we need non-witness data to be provided as metadata in the ACIR opcode */ AggregationObjectIndices create_honk_recursion_constraints(Builder& builder, - const HonkRecursionConstraint& input, + const RecursionConstraint& input, AggregationObjectIndices input_aggregation_object_indices, bool has_valid_witness_assignments) { @@ -159,6 +158,8 @@ AggregationObjectIndices create_honk_recursion_constraints(Builder& builder, using RecursiveVerificationKey = Flavor::VerificationKey; using RecursiveVerifier = bb::stdlib::recursion::honk::UltraRecursiveVerifier_; + ASSERT(input.proof_type == HONK_RECURSION); + // 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) @@ -179,7 +180,7 @@ AggregationObjectIndices create_honk_recursion_constraints(Builder& builder, auto field = field_ct::from_witness_index(&builder, idx); proof_fields.emplace_back(field); i++; - if (i == HonkRecursionConstraint::inner_public_input_offset) { + if (i == HONK_RECURSION_PUBLIC_INPUT_OFFSET) { for (const auto& idx : input.public_inputs) { auto field = field_ct::from_witness_index(&builder, idx); proof_fields.emplace_back(field); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp index 96736f339e0f..9fe6d5a0ca84 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp @@ -1,4 +1,5 @@ #pragma once +#include "barretenberg/dsl/acir_format/recursion_constraint.hpp" #include "barretenberg/stdlib/primitives/bigfield/bigfield.hpp" #include @@ -7,55 +8,12 @@ using Builder = bb::UltraCircuitBuilder; using namespace bb; -/** - * @brief HonkRecursionConstraint 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 honk proof. Is converted into circuit witness values (internal to the backend) - * @param is_aggregation_object_nonzero A flag to tell us whether the circuit has already recursively verified proofs - * (and therefore an aggregation object is present) - * @param public_input The index of the single public input - * @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) - * - * TODO(https://github.com/AztecProtocol/barretenberg/issues/996): Update these comments for Honk. - */ -struct HonkRecursionConstraint { - // In Honk, the proof starts with circuit_size, num_public_inputs, and pub_input_offset. We use this offset to keep - // track of where the public inputs start. - static constexpr size_t inner_public_input_offset = 3; - std::vector key; - std::vector proof; - std::vector public_inputs; - - friend bool operator==(HonkRecursionConstraint const& lhs, HonkRecursionConstraint const& rhs) = default; -}; +// In Honk, the proof starts with circuit_size, num_public_inputs, and pub_input_offset. We use this offset to keep +// track of where the public inputs start. +static constexpr size_t HONK_RECURSION_PUBLIC_INPUT_OFFSET = 3; AggregationObjectIndices create_honk_recursion_constraints(Builder& builder, - const HonkRecursionConstraint& input, + const RecursionConstraint& input, AggregationObjectIndices input_aggregation_object, bool has_valid_witness_assignments = false); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp index 5f89eb0fccd9..86ac113e76dc 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp @@ -138,7 +138,7 @@ class AcirHonkRecursionConstraint : public ::testing::Test { */ Builder create_outer_circuit(std::vector& inner_circuits) { - std::vector honk_recursion_constraints; + std::vector honk_recursion_constraints; size_t witness_offset = 0; std::vector> witness; @@ -155,7 +155,7 @@ class AcirHonkRecursionConstraint : public ::testing::Test { std::vector proof_witnesses = inner_proof; // where the inner public inputs start (after circuit_size, num_pub_inputs, pub_input_offset) - const size_t inner_public_input_offset = 3; + const size_t inner_public_input_offset = HONK_RECURSION_PUBLIC_INPUT_OFFSET; // - Save the public inputs so that we can set their values. // - Then truncate them from the proof because the ACIR API expects proofs without public inputs std::vector inner_public_input_values( @@ -206,10 +206,12 @@ class AcirHonkRecursionConstraint : public ::testing::Test { inner_public_inputs.push_back(static_cast(i + public_input_start_idx)); } - HonkRecursionConstraint honk_recursion_constraint{ + RecursionConstraint honk_recursion_constraint{ .key = key_indices, .proof = proof_indices, .public_inputs = inner_public_inputs, + .key_hash = 0, // not used + .proof_type = HONK_RECURSION, }; honk_recursion_constraints.push_back(honk_recursion_constraint); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp index e1a643dfd612..1a98fd15ff6f 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -6,6 +6,10 @@ namespace acir_format { +// Used to specify the type of recursive verifier via the proof_type specified by the RecursiveAggregation opcode from +// ACIR +enum PROOF_TYPE { PLONK_RECURSION, HONK_RECURSION }; + using namespace bb::plonk; using Builder = bb::UltraCircuitBuilder; @@ -43,6 +47,7 @@ using Builder = bb::UltraCircuitBuilder; * aggregation object in B’s public inputs as well as an input aggregation object that points to the object produced by * the previous recursion constraint in the circuit (the one that verifies A) * + * TODO(https://github.com/AztecProtocol/barretenberg/issues/996): Create similar comments for Honk. */ struct RecursionConstraint { // An aggregation state is represented by two G1 affine elements. Each G1 point has @@ -52,6 +57,7 @@ struct RecursionConstraint { std::vector proof; std::vector public_inputs; uint32_t key_hash; + uint32_t proof_type; friend bool operator==(RecursionConstraint const& lhs, RecursionConstraint const& rhs) = default; }; diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index abc6b33cb077..95b650f13d3f 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -209,6 +209,7 @@ Builder create_outer_circuit(std::vector& inner_circuits) .proof = proof_indices, .public_inputs = inner_public_inputs, .key_hash = key_hash_start_idx, + .proof_type = PLONK_RECURSION, }; recursion_constraints.push_back(recursion_constraint); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp index 0a4d49c56fed..dc4a52628c12 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp @@ -973,6 +973,7 @@ struct BlackBoxFuncCall { std::vector proof; std::vector public_inputs; Program::FunctionInput key_hash; + uint32_t proof_type; friend bool operator==(const RecursiveAggregation&, const RecursiveAggregation&); std::vector bincodeSerialize() const; @@ -3609,6 +3610,9 @@ inline bool operator==(const BlackBoxFuncCall::RecursiveAggregation& lhs, if (!(lhs.key_hash == rhs.key_hash)) { return false; } + if (!(lhs.proof_type == rhs.proof_type)) { + return false; + } return true; } @@ -3641,6 +3645,7 @@ void serde::Serializable::seria serde::Serializable::serialize(obj.proof, serializer); serde::Serializable::serialize(obj.public_inputs, serializer); serde::Serializable::serialize(obj.key_hash, serializer); + serde::Serializable::serialize(obj.proof_type, serializer); } template <> @@ -3653,6 +3658,7 @@ Program::BlackBoxFuncCall::RecursiveAggregation serde::Deserializable< obj.proof = serde::Deserializable::deserialize(deserializer); obj.public_inputs = serde::Deserializable::deserialize(deserializer); obj.key_hash = serde::Deserializable::deserialize(deserializer); + obj.proof_type = serde::Deserializable::deserialize(deserializer); return obj; } diff --git a/barretenberg/cpp/src/barretenberg/stdlib/honk_recursion/verifier/ultra_recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/stdlib/honk_recursion/verifier/ultra_recursive_verifier.cpp index de5111c83c35..c68f7421e25a 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/honk_recursion/verifier/ultra_recursive_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/honk_recursion/verifier/ultra_recursive_verifier.cpp @@ -48,7 +48,6 @@ UltraRecursiveVerifier_::AggregationObject UltraRecursiveVerifier_; using Transcript = typename Flavor::Transcript; - info("in honk recursive verifier"); transcript = std::make_shared(proof); RelationParams relation_parameters; diff --git a/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp b/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp index b16baf14d063..2f8835028693 100644 --- a/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp +++ b/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp @@ -933,6 +933,7 @@ namespace Program { std::vector proof; std::vector public_inputs; Program::FunctionInput key_hash; + uint32_t proof_type; friend bool operator==(const RecursiveAggregation&, const RecursiveAggregation&); std::vector bincodeSerialize() const; @@ -3149,6 +3150,7 @@ namespace Program { if (!(lhs.proof == rhs.proof)) { return false; } if (!(lhs.public_inputs == rhs.public_inputs)) { return false; } if (!(lhs.key_hash == rhs.key_hash)) { return false; } + if (!(lhs.proof_type == rhs.proof_type)) { return false; } return true; } @@ -3176,6 +3178,7 @@ void serde::Serializable::seria serde::Serializable::serialize(obj.proof, serializer); serde::Serializable::serialize(obj.public_inputs, serializer); serde::Serializable::serialize(obj.key_hash, serializer); + serde::Serializable::serialize(obj.proof_type, serializer); } template <> @@ -3186,6 +3189,7 @@ Program::BlackBoxFuncCall::RecursiveAggregation serde::Deserializable::deserialize(deserializer); obj.public_inputs = serde::Deserializable::deserialize(deserializer); obj.key_hash = serde::Deserializable::deserialize(deserializer); + obj.proof_type = serde::Deserializable::deserialize(deserializer); return obj; } diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs index 6a301ec5115e..333bab419c8c 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -159,6 +159,7 @@ pub enum BlackBoxFuncCall { /// The circuit implementing this opcode can use this hash to ensure that the /// key provided to the circuit matches the key produced by the circuit creator key_hash: FunctionInput, + proof_type: u32, }, BigIntAdd { lhs: u32, @@ -350,6 +351,7 @@ impl BlackBoxFuncCall { proof, public_inputs, key_hash, + proof_type: _, } => { let mut inputs = Vec::new(); inputs.extend(key.iter().copied()); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index b2a731064687..3431218b360f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -1426,6 +1426,30 @@ impl AcirContext { output_count = input_size + (16 - input_size % 16); (vec![], vec![F::from(output_count as u128)]) } + BlackBoxFunc::RecursiveAggregation => { + let proof_type_var = match inputs.pop() { + Some(domain_var) => domain_var.into_var()?, + None => { + return Err(RuntimeError::InternalError(InternalError::MissingArg { + name: "verify proof".to_string(), + arg: "proof type".to_string(), + call_stack: self.get_call_stack(), + })) + } + }; + + let proof_type_constant = match self.vars[&proof_type_var].as_constant() { + Some(proof_type_constant) => proof_type_constant, + None => { + return Err(RuntimeError::InternalError(InternalError::NotAConstant { + name: "proof type".to_string(), + call_stack: self.get_call_stack(), + })) + } + }; + + (vec![*proof_type_constant], Vec::new()) + } _ => (vec![], vec![]), }; // Allow constant inputs only for MSM for now diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 661371c5de6c..2e61a82d5b89 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -311,6 +311,7 @@ impl GeneratedAcir { proof: inputs[1].clone(), public_inputs: inputs[2].clone(), key_hash: inputs[3][0], + proof_type: constant_inputs[0].to_u128() as u32, }, BlackBoxFunc::BigIntAdd => BlackBoxFuncCall::BigIntAdd { lhs: constant_inputs[0].to_u128() as u32, diff --git a/noir/noir-repo/noir_stdlib/src/lib.nr b/noir/noir-repo/noir_stdlib/src/lib.nr index 2d559c431629..a0ae6ad19084 100644 --- a/noir/noir-repo/noir_stdlib/src/lib.nr +++ b/noir/noir-repo/noir_stdlib/src/lib.nr @@ -42,12 +42,33 @@ unconstrained pub fn println(input: T) { print_oracle(true, input); } -#[foreign(recursive_aggregation)] pub fn verify_proof( verification_key: [Field; N], proof: [Field; M], public_inputs: [Field; K], key_hash: Field +) { + verify_proof_internal(verification_key, proof, public_inputs, key_hash, 0); +} + +pub fn verify_proof_with_type( + verification_key: [Field; N], + proof: [Field; M], + public_inputs: [Field; K], + key_hash: Field, + proof_type: u32 +) { + crate::assert_constant(proof_type); + verify_proof_internal(verification_key, proof, public_inputs, key_hash, proof_type); +} + +#[foreign(recursive_aggregation)] +fn verify_proof_internal( + verification_key: [Field; N], + proof: [Field; M], + public_inputs: [Field; K], + key_hash: Field, + proof_type: u32 ) {} // Asserts that the given value is known at compile-time. diff --git a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr b/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr index 17adc68c056e..902602f3dfd6 100644 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/verify_honk_proof/src/main.nr @@ -1,6 +1,7 @@ // This circuit aggregates a single Honk proof from `assert_statement_recursive`. global SIZE_OF_PROOF_IF_LOGN_IS_28 : u32 = 409; +global HONK_IDENTIFIER : u32 = 1; fn main( verification_key: [Field; 120], // This is the proof without public inputs attached. @@ -12,5 +13,12 @@ fn main( // I believe we want to eventually make it public too though. key_hash: Field ) { - std::verify_proof(verification_key, proof, public_inputs, key_hash); + // Proof type input specified as 1 to indicate honk + std::verify_proof_with_type( + verification_key, + proof, + public_inputs, + key_hash, + HONK_IDENTIFIER + ); } diff --git a/noir/verify_honk_proof/src/main.nr b/noir/verify_honk_proof/src/main.nr index 10ea45777501..d7c08cb5c2c5 100644 --- a/noir/verify_honk_proof/src/main.nr +++ b/noir/verify_honk_proof/src/main.nr @@ -11,6 +11,7 @@ fn main( // This is currently not public. It is fine given that the vk is a part of the circuit definition. // I believe we want to eventually make it public too though. key_hash: Field + // WORKTODO: Can we delete this program? noir needed it for some reason but its not used in bberg ) { std::verify_proof( verification_key,