|
3 | 3 | #include "barretenberg/dsl/acir_format/pedersen.hpp"
|
4 | 4 | #include "barretenberg/dsl/acir_format/recursion_constraint.hpp"
|
5 | 5 | #include "barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp"
|
| 6 | +#include <cstddef> |
6 | 7 |
|
7 | 8 | namespace acir_format {
|
8 | 9 |
|
@@ -158,24 +159,56 @@ void build_constraints(Builder& builder, acir_format const& constraint_system, b
|
158 | 159 | // These are set and modified whenever we encounter a recursion opcode
|
159 | 160 | //
|
160 | 161 | // 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 |
| 162 | + // TODO(maxim): Check if this is always the case. ie I won't receive a proof that will set the first |
| 163 | + // TODO(maxim): input_aggregation_object to be non-zero. |
| 164 | + // TODO(maxim): if not, we can add input_aggregation_object to the proof too for all recursive proofs |
| 165 | + // TODO(maxim): This might be the case for proof trees where the proofs are created on different machines |
165 | 166 | std::array<uint32_t, RecursionConstraint::AGGREGATION_OBJECT_SIZE> current_input_aggregation_object = {
|
166 | 167 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
167 | 168 | };
|
168 | 169 | std::array<uint32_t, RecursionConstraint::AGGREGATION_OBJECT_SIZE> current_output_aggregation_object = {
|
169 | 170 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
170 | 171 | };
|
171 | 172 |
|
| 173 | + // Get the size of proof with no public inputs prepended to it |
| 174 | + // This is used while processing recursion constraints to determine whether |
| 175 | + // the proof we are verifying contains a recursive proof itself |
| 176 | + auto proof_size_no_pub_inputs = recursion_proof_size_without_public_inputs(); |
| 177 | + |
172 | 178 | // Add recursion constraints
|
173 |
| - for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { |
174 |
| - auto& constraint = constraint_system.recursion_constraints[i]; |
| 179 | + for (auto constraint : constraint_system.recursion_constraints) { |
| 180 | + // A proof passed into the constraint should be stripped of its public inputs, except in the case where a |
| 181 | + // proof contains an aggregation object itself. We refer to this as the `nested_aggregation_object`. The |
| 182 | + // verifier circuit requires that the indices to a nested proof aggregation state are a circuit constant. |
| 183 | + // The user tells us they how they want these constants set by keeping the nested aggregation object |
| 184 | + // attached to the proof as public inputs. As this is the only object that can prepended to the proof if the |
| 185 | + // proof is above the expected size (with public inputs stripped) |
| 186 | + std::array<uint32_t, RecursionConstraint::AGGREGATION_OBJECT_SIZE> nested_aggregation_object = {}; |
| 187 | + // If the proof has public inputs attached to it, we should handle setting the nested aggregation object |
| 188 | + if (constraint.proof.size() > proof_size_no_pub_inputs) { |
| 189 | + // The public inputs attached to a proof should match the aggregation object in size |
| 190 | + ASSERT(constraint.proof.size() - proof_size_no_pub_inputs == |
| 191 | + RecursionConstraint::AGGREGATION_OBJECT_SIZE); |
| 192 | + for (size_t i = 0; i < RecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { |
| 193 | + // Set the nested aggregation object indices to the current size of the public inputs |
| 194 | + // This way we know that the nested aggregation object indices will always be the last |
| 195 | + // indices of the public inputs |
| 196 | + nested_aggregation_object[i] = static_cast<uint32_t>(constraint.public_inputs.size()); |
| 197 | + // Attach the nested aggregation object to the end of the public inputs to fill in |
| 198 | + // the slot where the nested aggregation object index will point into |
| 199 | + constraint.public_inputs.emplace_back(constraint.proof[i]); |
| 200 | + } |
| 201 | + // Remove the aggregation object so that they can be handled as normal public inputs |
| 202 | + // in they way taht the recursion constraint expects |
| 203 | + constraint.proof.erase(constraint.proof.begin(), |
| 204 | + constraint.proof.begin() + |
| 205 | + static_cast<std::ptrdiff_t>(RecursionConstraint::AGGREGATION_OBJECT_SIZE)); |
| 206 | + } |
| 207 | + |
175 | 208 | current_output_aggregation_object = create_recursion_constraints(builder,
|
176 | 209 | constraint,
|
177 | 210 | current_input_aggregation_object,
|
178 |
| - constraint.nested_aggregation_object, |
| 211 | + nested_aggregation_object, |
179 | 212 | has_valid_witness_assignments);
|
180 | 213 | current_input_aggregation_object = current_output_aggregation_object;
|
181 | 214 | }
|
@@ -241,25 +274,26 @@ void create_circuit_with_witness(Builder& builder, acir_format const& constraint
|
241 | 274 |
|
242 | 275 | /**
|
243 | 276 | * @brief Apply an offset to the indices stored in the wires
|
244 |
| - * @details This method is needed due to the following: Noir constructs "wires" as indices into a "witness" vector. This |
245 |
| - * is analogous to the wires and variables vectors in bberg builders. Were it not for the addition of constant variables |
246 |
| - * in the constructors of a builder (e.g. zero), we would simply have noir.wires = builder.wires and noir.witness = |
247 |
| - * builder.variables. To account for k-many constant variables in the first entries of the variables array, we have |
248 |
| - * something like variables = variables.append(noir.witness). Accordingly, the indices in noir.wires have to be |
249 |
| - * incremented to account for the offset at which noir.wires was placed into variables. |
| 277 | + * @details This method is needed due to the following: Noir constructs "wires" as indices into a "witness" vector. |
| 278 | + * This is analogous to the wires and variables vectors in bberg builders. Were it not for the addition of constant |
| 279 | + * variables in the constructors of a builder (e.g. zero), we would simply have noir.wires = builder.wires and |
| 280 | + * noir.witness = builder.variables. To account for k-many constant variables in the first entries of the variables |
| 281 | + * array, we have something like variables = variables.append(noir.witness). Accordingly, the indices in noir.wires |
| 282 | + * have to be incremented to account for the offset at which noir.wires was placed into variables. |
250 | 283 | *
|
251 | 284 | * @tparam Builder
|
252 | 285 | * @param builder
|
253 | 286 | */
|
254 | 287 | template <typename Builder> void apply_wire_index_offset(Builder& builder)
|
255 | 288 | {
|
256 |
| - // For now, noir has a hard coded witness index offset = 1. Once this is removed, this pre-applied offset goes away |
| 289 | + // For now, noir has a hard coded witness index offset = 1. Once this is removed, this pre-applied offset goes |
| 290 | + // away |
257 | 291 | const uint32_t pre_applied_noir_offset = 1;
|
258 | 292 | auto offset = static_cast<uint32_t>(builder.num_vars_added_in_constructor - pre_applied_noir_offset);
|
259 | 293 | info("Applying offset = ", offset);
|
260 | 294 |
|
261 |
| - // Apply the offset to the indices stored the wires that were generated from acir. (Do not apply the offset to those |
262 |
| - // values that were added in the builder constructor). |
| 295 | + // Apply the offset to the indices stored the wires that were generated from acir. (Do not apply the offset to |
| 296 | + // those values that were added in the builder constructor). |
263 | 297 | size_t start_index = builder.num_vars_added_in_constructor;
|
264 | 298 | for (auto& wire : builder.wires) {
|
265 | 299 | for (size_t idx = start_index; idx < wire.size(); ++idx) {
|
|
0 commit comments