Skip to content

Commit fde0ac3

Browse files
vezenovmkevaundray
andauthored
chore: Cleanup recursion interface (AztecProtocol#3744)
This is a recreation of this PR (AztecProtocol#3528) to handle PR AztecProtocol#3729 # Checklist: Remove the checklist to signal you've completed it. Enable auto-merge if the PR is ready to merge. - [ ] If the pull request requires a cryptography review (e.g. cryptographic algorithm implementations) I have added the 'crypto' tag. - [ ] I have reviewed my diff in github, line by line and removed unexpected formatting changes, testing logs, or commented-out code. - [ ] Every change is related to the PR description. - [ ] I have [linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) this pull request to relevant issues (if any exist). --------- Co-authored-by: kevaundray <kevtheappdev@gmail.com>
1 parent 1c68e3b commit fde0ac3

File tree

10 files changed

+137
-54
lines changed

10 files changed

+137
-54
lines changed

barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp

+39-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "acir_format.hpp"
22
#include "barretenberg/common/log.hpp"
33
#include "barretenberg/dsl/acir_format/pedersen.hpp"
4+
#include "barretenberg/dsl/acir_format/recursion_constraint.hpp"
45
#include "barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp"
56

67
namespace acir_format {
@@ -149,23 +150,52 @@ void build_constraints(Builder& builder, acir_format const& constraint_system, b
149150
create_block_constraints(builder, constraint, has_valid_witness_assignments);
150151
}
151152

152-
// Add recursion constraints
153153
// TODO(https://github.com/AztecProtocol/barretenberg/issues/817): disable these for UGH for now since we're not yet
154154
// dealing with proper recursion
155155
if constexpr (IsGoblinBuilder<Builder>) {
156156
info("WARNING: this circuit contains recursion_constraints!");
157157
} else {
158+
// These are set and modified whenever we encounter a recursion opcode
159+
//
160+
// These should not be set by the caller
161+
// TODO: Check if this is always the case. ie I won't receive a proof that will set the first
162+
// TODO input_aggregation_object to be non-zero.
163+
// TODO: if not, we can add input_aggregation_object to the proof too for all recursive proofs
164+
// TODO: This might be the case for proof trees where the proofs are created on different machines
165+
std::array<uint32_t, RecursionConstraint::AGGREGATION_OBJECT_SIZE> current_input_aggregation_object = {
166+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
167+
};
168+
std::array<uint32_t, RecursionConstraint::AGGREGATION_OBJECT_SIZE> current_output_aggregation_object = {
169+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
170+
};
171+
172+
// Add recursion constraints
158173
for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) {
159174
auto& constraint = constraint_system.recursion_constraints[i];
160-
create_recursion_constraints(builder, constraint, has_valid_witness_assignments);
161-
162-
// make sure the verification key records the public input indices of the final recursion output (N.B. up to
163-
// the ACIR description to make sure that the final output aggregation object wires are public inputs!)
164-
if (i == constraint_system.recursion_constraints.size() - 1) {
165-
std::vector<uint32_t> proof_output_witness_indices(constraint.output_aggregation_object.begin(),
166-
constraint.output_aggregation_object.end());
167-
builder.set_recursive_proof(proof_output_witness_indices);
175+
current_output_aggregation_object = create_recursion_constraints(builder,
176+
constraint,
177+
current_input_aggregation_object,
178+
constraint.nested_aggregation_object,
179+
has_valid_witness_assignments);
180+
current_input_aggregation_object = current_output_aggregation_object;
181+
}
182+
183+
// Now that the circuit has been completely built, we add the output aggregation as public
184+
// inputs.
185+
if (!constraint_system.recursion_constraints.empty()) {
186+
187+
// First add the output aggregation object as public inputs
188+
// Set the indices as public inputs because they are no longer being
189+
// created in ACIR
190+
for (const auto& idx : current_output_aggregation_object) {
191+
builder.set_public_input(idx);
168192
}
193+
194+
// Make sure the verification key records the public input indices of the
195+
// final recursion output.
196+
std::vector<uint32_t> proof_output_witness_indices(current_output_aggregation_object.begin(),
197+
current_output_aggregation_object.end());
198+
builder.set_recursive_proof(proof_output_witness_indices);
169199
}
170200
}
171201
}

barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp

+34-18
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "barretenberg/plonk/transcript/transcript_wrappers.hpp"
44
#include "barretenberg/stdlib/recursion/aggregation_state/aggregation_state.hpp"
55
#include "barretenberg/stdlib/recursion/verifier/verifier.hpp"
6+
#include <cstddef>
67

78
namespace acir_format {
89

@@ -28,12 +29,19 @@ void generate_dummy_proof() {}
2829
* We would either need a separate ACIR opcode where inner_proof_contains_recursive_proof = true,
2930
* or we need non-witness data to be provided as metadata in the ACIR opcode
3031
*/
31-
template <typename Builder>
32-
void create_recursion_constraints(Builder& builder,
33-
const RecursionConstraint& input,
34-
bool has_valid_witness_assignments)
32+
std::array<uint32_t, RecursionConstraint::AGGREGATION_OBJECT_SIZE> create_recursion_constraints(
33+
Builder& builder,
34+
const RecursionConstraint& input,
35+
std::array<uint32_t, RecursionConstraint::AGGREGATION_OBJECT_SIZE> input_aggregation_object,
36+
// TODO: does this need to be a part of the recursion opcode?
37+
// TODO: or can we figure it out from the vk?
38+
// TODO: either way we could probably have the user explicitly provide it
39+
// TODO: in Noir.
40+
// Note: this is not being used in Noir at the moment
41+
std::array<uint32_t, RecursionConstraint::AGGREGATION_OBJECT_SIZE> nested_aggregation_object,
42+
bool has_valid_witness_assignments)
3543
{
36-
const auto& nested_aggregation_indices = input.nested_aggregation_object;
44+
const auto& nested_aggregation_indices = nested_aggregation_object;
3745
bool nested_aggregation_indices_all_zero = true;
3846
for (const auto& idx : nested_aggregation_indices) {
3947
nested_aggregation_indices_all_zero &= (idx == 0);
@@ -47,8 +55,12 @@ void create_recursion_constraints(Builder& builder,
4755
const std::vector<fr> dummy_key = export_dummy_key_in_recursion_format(
4856
PolynomialManifest(Builder::CIRCUIT_TYPE), inner_proof_contains_recursive_proof);
4957
const auto manifest = Composer::create_manifest(input.public_inputs.size());
50-
const std::vector<barretenberg::fr> dummy_proof =
58+
std::vector<barretenberg::fr> dummy_proof =
5159
export_dummy_transcript_in_recursion_format(manifest, inner_proof_contains_recursive_proof);
60+
61+
// Remove the public inputs from the dummy proof
62+
dummy_proof.erase(dummy_proof.begin(),
63+
dummy_proof.begin() + static_cast<std::ptrdiff_t>(input.public_inputs.size()));
5264
for (size_t i = 0; i < input.proof.size(); ++i) {
5365
const auto proof_field_idx = input.proof[i];
5466
// if we do NOT have a witness assignment (i.e. are just building the proving/verification keys),
@@ -74,7 +86,7 @@ void create_recursion_constraints(Builder& builder,
7486
// Construct an in-circuit representation of the verification key.
7587
// For now, the v-key is a circuit constant and is fixed for the circuit.
7688
// (We may need a separate recursion opcode for this to vary, or add more config witnesses to this opcode)
77-
const auto& aggregation_input = input.input_aggregation_object;
89+
const auto& aggregation_input = input_aggregation_object;
7890
aggregation_state_ct previous_aggregation;
7991

8092
// If we have previously recursively verified proofs, `is_aggregation_object_nonzero = true`
@@ -113,7 +125,13 @@ void create_recursion_constraints(Builder& builder,
113125
}
114126

115127
std::vector<field_ct> proof_fields;
116-
proof_fields.reserve(input.proof.size());
128+
// Prepend the public inputs to the proof fields because this is how the
129+
// core barretenberg library processes proofs (with the public inputs first and not separated)
130+
proof_fields.reserve(input.proof.size() + input.public_inputs.size());
131+
for (const auto& idx : input.public_inputs) {
132+
auto field = field_ct::from_witness_index(&builder, idx);
133+
proof_fields.emplace_back(field);
134+
}
117135
for (const auto& idx : input.proof) {
118136
auto field = field_ct::from_witness_index(&builder, idx);
119137
proof_fields.emplace_back(field);
@@ -137,12 +155,14 @@ void create_recursion_constraints(Builder& builder,
137155
result.public_inputs[i].assert_equal(field_ct::from_witness_index(&builder, input.public_inputs[i]));
138156
}
139157

140-
// Assign the recursive proof outputs to `output_aggregation_object`
141-
for (size_t i = 0; i < result.proof_witness_indices.size(); ++i) {
142-
const auto lhs = field_ct::from_witness_index(&builder, result.proof_witness_indices[i]);
143-
const auto rhs = field_ct::from_witness_index(&builder, input.output_aggregation_object[i]);
144-
lhs.assert_equal(rhs);
145-
}
158+
// We want to return an array, so just copy the vector into the array
159+
ASSERT(result.proof_witness_indices.size() == RecursionConstraint::AGGREGATION_OBJECT_SIZE);
160+
std::array<uint32_t, RecursionConstraint::AGGREGATION_OBJECT_SIZE> resulting_output_aggregation_object;
161+
std::copy(result.proof_witness_indices.begin(),
162+
result.proof_witness_indices.begin() + RecursionConstraint::AGGREGATION_OBJECT_SIZE,
163+
resulting_output_aggregation_object.begin());
164+
165+
return resulting_output_aggregation_object;
146166
}
147167

148168
/**
@@ -350,8 +370,4 @@ G1AsFields export_g1_affine_element_as_fields(const barretenberg::g1::affine_ele
350370
return G1AsFields{ x_lo, x_hi, y_lo, y_hi };
351371
}
352372

353-
template void create_recursion_constraints<UltraCircuitBuilder>(UltraCircuitBuilder& builder,
354-
const RecursionConstraint& input,
355-
bool has_valid_witness_assignments);
356-
357373
} // namespace acir_format

barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp

+11-4
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,24 @@ struct RecursionConstraint {
5353
std::vector<uint32_t> proof;
5454
std::vector<uint32_t> public_inputs;
5555
uint32_t key_hash;
56+
// TODO:This is now unused, but we keep it here for backwards compatibility
5657
std::array<uint32_t, AGGREGATION_OBJECT_SIZE> input_aggregation_object;
58+
// TODO: This is now unused, but we keep it here for backwards compatibility
5759
std::array<uint32_t, AGGREGATION_OBJECT_SIZE> output_aggregation_object;
60+
// TODO: This is currently not being used on the Noir level at all
61+
// TODO: we don't have a way to specify that the proof we are creating contains a
62+
// TODO: aggregation object (ie it is also verifying a proof)
5863
std::array<uint32_t, AGGREGATION_OBJECT_SIZE> nested_aggregation_object;
5964

6065
friend bool operator==(RecursionConstraint const& lhs, RecursionConstraint const& rhs) = default;
6166
};
6267

63-
template <typename Builder>
64-
void create_recursion_constraints(Builder& builder,
65-
const RecursionConstraint& input,
66-
bool has_valid_witness_assignments = false);
68+
std::array<uint32_t, RecursionConstraint::AGGREGATION_OBJECT_SIZE> create_recursion_constraints(
69+
Builder& builder,
70+
const RecursionConstraint& input,
71+
std::array<uint32_t, RecursionConstraint::AGGREGATION_OBJECT_SIZE> input_aggregation_object,
72+
std::array<uint32_t, RecursionConstraint::AGGREGATION_OBJECT_SIZE> nested_aggregation_object,
73+
bool has_valid_witness_assignments = false);
6774

6875
std::vector<barretenberg::fr> export_key_in_recursion_format(std::shared_ptr<verification_key> const& vkey);
6976
std::vector<barretenberg::fr> export_dummy_key_in_recursion_format(const PolynomialManifest& polynomial_manifest,

barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp

+16-4
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,15 @@ Builder create_outer_circuit(std::vector<Builder>& inner_circuits)
146146
transcript::HashType::PedersenBlake3s,
147147
16);
148148

149-
const std::vector<barretenberg::fr> proof_witnesses = export_transcript_in_recursion_format(transcript);
149+
std::vector<barretenberg::fr> proof_witnesses = export_transcript_in_recursion_format(transcript);
150+
// - Save the public inputs so that we can set their values.
151+
// - Then truncate them from the proof because the ACIR API expects proofs without public inputs
152+
153+
std::vector<barretenberg::fr> inner_public_input_values(
154+
proof_witnesses.begin(), proof_witnesses.begin() + static_cast<std::ptrdiff_t>(num_inner_public_inputs));
155+
proof_witnesses.erase(proof_witnesses.begin(),
156+
proof_witnesses.begin() + static_cast<std::ptrdiff_t>(num_inner_public_inputs));
157+
150158
const std::vector<barretenberg::fr> key_witnesses = export_key_in_recursion_format(inner_verifier.key);
151159

152160
const uint32_t key_hash_start_idx = static_cast<uint32_t>(witness_offset);
@@ -202,14 +210,18 @@ Builder create_outer_circuit(std::vector<Builder>& inner_circuits)
202210
for (const auto& wit : key_witnesses) {
203211
witness.emplace_back(wit);
204212
}
213+
// Set the values for the inner public inputs
214+
// Note: this is confusing, but we minus one here due to the fact that the
215+
// witness values have not taken into account that zero is taken up by the zero_idx
216+
for (size_t i = 0; i < num_inner_public_inputs; ++i) {
217+
witness[inner_public_inputs[i] - 1] = inner_public_input_values[i];
218+
}
205219
witness_offset = key_indices_start_idx + key_witnesses.size();
206220
circuit_idx++;
207221
}
208222

209-
std::vector<uint32_t> public_inputs(output_aggregation_object.begin(), output_aggregation_object.end());
210-
211223
acir_format constraint_system{ .varnum = static_cast<uint32_t>(witness.size() + 1),
212-
.public_inputs = public_inputs,
224+
.public_inputs = {},
213225
.logic_constraints = {},
214226
.range_constraints = {},
215227
.sha256_constraints = {},

barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,16 @@ bool AcirComposer::verify_proof(std::vector<uint8_t> const& proof, bool is_recur
130130
// Hack. Shouldn't need to do this. 2144 is size with no public inputs.
131131
builder_.public_inputs.resize((proof.size() - 2144) / 32);
132132

133+
// TODO: We could get rid of this, if we made the Noir program specify whether something should be
134+
// TODO: created with the recursive setting or not. ie:
135+
//
136+
// #[recursive_friendly]
137+
// fn main() {}
138+
// would put in the ACIR that we want this to be recursion friendly with a flag maybe and the backend
139+
// would set the is_recursive flag to be true.
140+
// This would eliminate the need for nargo to have a --recursive flag
141+
//
142+
// End result is that we may just be able to get it off of builder_, like builder_.is_recursive_friendly
133143
if (is_recursive) {
134144
auto verifier = composer.create_verifier(builder_);
135145
return verifier.verify_proof({ proof });
@@ -152,6 +162,8 @@ std::string AcirComposer::get_solidity_verifier()
152162
}
153163

154164
/**
165+
* TODO: We should change this to return a proof without public inputs, since that is what std::verify_proof
166+
* TODO: takes.
155167
* @brief Takes in a proof buffer and converts into a vector of field elements.
156168
* The Recursion opcode requires the proof serialized as a vector of witnesses.
157169
* Use this method to get the witness values!

barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/circuit_builder_base.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -347,11 +347,15 @@ template <typename FF_> class CircuitBuilderBase {
347347

348348
for (const auto& idx : proof_output_witness_indices) {
349349
set_public_input(idx);
350+
// Why is it adding the size of the public input instead of the idx?
350351
recursive_proof_public_input_indices.push_back((uint32_t)(public_inputs.size() - 1));
351352
}
352353
}
353354

354355
/**
356+
* TODO: We can remove this and use `add_recursive_proof` once my question has been addressed
357+
* TODO: using `add_recursive_proof` also means that we will need to remove the cde which is
358+
* TODO: adding the public_inputs
355359
* @brief Update recursive_proof_public_input_indices with existing public inputs that represent a recursive proof
356360
*
357361
* @param proof_output_witness_indices
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)