Skip to content

Commit a876ab8

Browse files
authored
feat: Correct circuit construction from acir (AztecProtocol#3757)
This work attempts to add a robust means for construction of bberg circuits from acir data. The difficulty comes primarily from different assumptions about constant variables added in the builder constructors. The problem is essentially that constraints are constructed from acir in two ways: directly and indirectly. Directly means the witness values themselves are known at the point of acir generation. In this case gates are specified directly via indices into a "witness" array. Most constraints are indirect in the sense that bberg is presented with an acir opcode, a sha hash say, and has to turn that into constraints using internal bberg mechanisms. This leads to issues when the "witness" array (and acir object) and the "variables" array (a bberg builder object) are offset from one another, as can happen when constant variables are added in the bberg builder constructors. The issue is further complicated by the fact that noir applies a PRE-offset (see [here](noir-lang/noir#3813)) to the indices in the directly specified gates to account for the fact that the Ultra builder historically added a single constant in its constructor. The GUH builder adds 3 more, which is how this issue arose in the first place. However the issue would have come up regardless once we removed that +1 from noir which everyone agrees should not be there. The solution was to add a new builder constructor that takes some data from acir (witness, public_inputs, etc.), populates the analogous structures in the builder, and then adds the necessary constant variables. This means that the indices encoded into explicit gates from acir correctly index into builder.variables, regardless of how many constants are added subsequently. This solution will also work once we remove the +1 from noir, with one additional tweak in bberg that is clearly indicated in the code. Closes AztecProtocol/barretenberg#815
1 parent 5b1e9f2 commit a876ab8

15 files changed

+128
-95
lines changed

barretenberg/acir_tests/Dockerfile.bb

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ COPY . .
1111
# This ensures we test independent pk construction through real/garbage witness data paths.
1212
RUN FLOW=prove_then_verify ./run_acir_tests.sh
1313
# TODO(https://github.com/AztecProtocol/barretenberg/issues/811) make this able to run the default test
14-
RUN FLOW=prove_and_verify_goblin ./run_acir_tests.sh assert_statement
14+
RUN FLOW=prove_and_verify_goblin ./run_acir_tests.sh
1515
# Run 1_mul through native bb build, all_cmds flow, to test all cli args.
1616
RUN VERBOSE=1 FLOW=all_cmds ./run_acir_tests.sh 1_mul

barretenberg/acir_tests/Dockerfile.bb.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ ENV VERBOSE=1
1515
# Run double_verify_proof through bb.js on node to check 512k support.
1616
RUN BIN=../ts/dest/node/main.js FLOW=prove_then_verify ./run_acir_tests.sh double_verify_proof
1717
# TODO(https://github.com/AztecProtocol/barretenberg/issues/811) make this able to run double_verify_proof
18-
RUN BIN=../ts/dest/node/main.js FLOW=prove_and_verify_goblin ./run_acir_tests.sh assert_statement
18+
RUN BIN=../ts/dest/node/main.js FLOW=prove_and_verify_goblin ./run_acir_tests.sh
1919
# Run 1_mul through bb.js build, all_cmds flow, to test all cli args.
2020
RUN BIN=../ts/dest/node/main.js FLOW=all_cmds ./run_acir_tests.sh 1_mul
2121
# Run double_verify_proof through bb.js on chrome testing multi-threaded browser support.

barretenberg/acir_tests/flows/prove_and_verify_goblin.sh

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@ set -eu
33

44
VFLAG=${VERBOSE:+-v}
55

6-
$BIN prove_and_verify_goblin $VFLAG -c $CRS_PATH -b ./target/acir.gz
6+
$BIN prove_and_verify_goblin $VFLAG -c $CRS_PATH -b ./target/acir.gz
7+
8+
# This command can be used to run all of the tests in sequence with the debugger
9+
# lldb-16 -o run -b -- $BIN prove_and_verify_goblin $VFLAG -c $CRS_PATH -b ./target/acir.gz

barretenberg/cpp/src/barretenberg/bb/main.cpp

+5-9
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,18 @@ acir_proofs::AcirComposer init(acir_format::acir_format& constraint_system)
5252

5353
void init_reference_strings()
5454
{
55-
// TODO(https://github.com/AztecProtocol/barretenberg/issues/811): Don't hardcode subgroup size
56-
size_t subgroup_size = 32768;
55+
// TODO(https://github.com/AztecProtocol/barretenberg/issues/811): Don't hardcode subgroup size. Currently set to
56+
// max circuit size present in acir tests suite.
57+
size_t hardcoded_subgroup_size_hack = 262144;
5758

5859
// TODO(https://github.com/AztecProtocol/barretenberg/issues/811) reduce duplication with above
5960
// Must +1!
60-
auto g1_data = get_bn254_g1_data(CRS_PATH, subgroup_size + 1);
61+
auto g1_data = get_bn254_g1_data(CRS_PATH, hardcoded_subgroup_size_hack + 1);
6162
auto g2_data = get_bn254_g2_data(CRS_PATH);
6263
srs::init_crs_factory(g1_data, g2_data);
6364

6465
// Must +1!
65-
auto grumpkin_g1_data = get_grumpkin_g1_data(CRS_PATH, subgroup_size + 1);
66+
auto grumpkin_g1_data = get_grumpkin_g1_data(CRS_PATH, hardcoded_subgroup_size_hack + 1);
6667
srs::init_grumpkin_crs_factory(grumpkin_g1_data);
6768
}
6869

@@ -145,23 +146,18 @@ bool proveAndVerifyGoblin(const std::string& bytecodePath,
145146
const std::string& witnessPath,
146147
[[maybe_unused]] bool recursive)
147148
{
148-
info("Construct constraint_system and witness.");
149149
auto constraint_system = get_constraint_system(bytecodePath);
150150
auto witness = get_witness(witnessPath);
151151

152152
init_reference_strings();
153153

154-
info("Construct goblin circuit from constraint system and witness.");
155154
acir_proofs::AcirComposer acir_composer;
156155
acir_composer.create_goblin_circuit(constraint_system, witness);
157156

158-
info("Construct goblin proof.");
159157
auto proof = acir_composer.create_goblin_proof();
160158

161-
info("verify_goblin_proof.");
162159
auto verified = acir_composer.verify_goblin_proof(proof);
163160

164-
vinfo("verified: ", verified);
165161
return verified;
166162
}
167163

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

+4-33
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,9 @@ void build_constraints(Builder& builder, acir_format const& constraint_system, b
154154
// TODO(https://github.com/AztecProtocol/barretenberg/issues/817): disable these for UGH for now since we're not yet
155155
// dealing with proper recursion
156156
if constexpr (IsGoblinBuilder<Builder>) {
157-
info("WARNING: this circuit contains recursion_constraints!");
157+
if (constraint_system.recursion_constraints.size() > 0) {
158+
info("WARNING: this circuit contains recursion_constraints!");
159+
}
158160
} else {
159161
// These are set and modified whenever we encounter a recursion opcode
160162
//
@@ -272,36 +274,6 @@ void create_circuit_with_witness(Builder& builder, acir_format const& constraint
272274
build_constraints(builder, constraint_system, true);
273275
}
274276

275-
/**
276-
* @brief Apply an offset to the indices stored in the wires
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.
283-
*
284-
* @tparam Builder
285-
* @param builder
286-
*/
287-
template <typename Builder> void apply_wire_index_offset(Builder& builder)
288-
{
289-
// For now, noir has a hard coded witness index offset = 1. Once this is removed, this pre-applied offset goes
290-
// away
291-
const uint32_t pre_applied_noir_offset = 1;
292-
auto offset = static_cast<uint32_t>(builder.num_vars_added_in_constructor - pre_applied_noir_offset);
293-
info("Applying offset = ", offset);
294-
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).
297-
size_t start_index = builder.num_vars_added_in_constructor;
298-
for (auto& wire : builder.wires) {
299-
for (size_t idx = start_index; idx < wire.size(); ++idx) {
300-
wire[idx] += offset;
301-
}
302-
}
303-
}
304-
305277
template UltraCircuitBuilder create_circuit<UltraCircuitBuilder>(const acir_format& constraint_system,
306278
size_t size_hint);
307279
template void create_circuit_with_witness<UltraCircuitBuilder>(UltraCircuitBuilder& builder,
@@ -310,7 +282,6 @@ template void create_circuit_with_witness<UltraCircuitBuilder>(UltraCircuitBuild
310282
template void create_circuit_with_witness<GoblinUltraCircuitBuilder>(GoblinUltraCircuitBuilder& builder,
311283
acir_format const& constraint_system,
312284
WitnessVector const& witness);
313-
template void apply_wire_index_offset<GoblinUltraCircuitBuilder>(GoblinUltraCircuitBuilder& builder);
314-
template void apply_wire_index_offset<UltraCircuitBuilder>(UltraCircuitBuilder& builder);
285+
template void build_constraints<GoblinUltraCircuitBuilder>(GoblinUltraCircuitBuilder&, acir_format const&, bool);
315286

316287
} // namespace acir_format

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ Builder create_circuit_with_witness(const acir_format& constraint_system,
8686
template <typename Builder>
8787
void create_circuit_with_witness(Builder& builder, const acir_format& constraint_system, WitnessVector const& witness);
8888

89-
template <typename Builder> void apply_wire_index_offset(Builder& builder);
89+
template <typename Builder>
90+
void build_constraints(Builder& builder, acir_format const& constraint_system, bool has_valid_witness_assignments);
9091

9192
} // namespace acir_format

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

+17-8
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,26 @@ std::vector<uint8_t> AcirComposer::create_proof(acir_format::acir_format& constr
8181
void AcirComposer::create_goblin_circuit(acir_format::acir_format& constraint_system,
8282
acir_format::WitnessVector& witness)
8383
{
84-
// Provide the builder with the op queue owned by the goblin instance
85-
goblin_builder_.op_queue = goblin.op_queue;
86-
87-
create_circuit_with_witness(goblin_builder_, constraint_system, witness);
84+
// The public inputs in constraint_system do not index into "witness" but rather into the future "variables" which
85+
// it assumes will be equal to witness but with a prepended zero. We want to remove this +1 so that public_inputs
86+
// properly indexes into witness because we're about to make calls like add_variable(witness[public_inputs[idx]]).
87+
// Once the +1 is removed from noir, this correction can be removed entirely and we can use
88+
// constraint_system.public_inputs directly.
89+
const uint32_t pre_applied_noir_offset = 1;
90+
std::vector<uint32_t> corrected_public_inputs;
91+
for (const auto& index : constraint_system.public_inputs) {
92+
corrected_public_inputs.emplace_back(index - pre_applied_noir_offset);
93+
}
8894

89-
info("after create_circuit_with_witness: num_gates = ", goblin_builder_.num_gates);
95+
// Construct a builder using the witness and public input data from acir
96+
goblin_builder_ =
97+
acir_format::GoblinBuilder{ goblin.op_queue, witness, corrected_public_inputs, constraint_system.varnum };
9098

91-
// Correct for the addition of const variables in the builder constructor
92-
acir_format::apply_wire_index_offset(goblin_builder_);
99+
// Populate constraints in the builder via the data in constraint_system
100+
acir_format::build_constraints(goblin_builder_, constraint_system, true);
93101

94-
// Add some arbitrary op gates to ensure the associated polynomials are non-zero
102+
// TODO(https://github.com/AztecProtocol/barretenberg/issues/817): Add some arbitrary op gates to ensure the
103+
// associated polynomials are non-zero and to give ECCVM and Translator some ECC ops to process.
95104
GoblinTestingUtils::construct_goblin_ecc_op_circuit(goblin_builder_);
96105
}
97106

barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp

+4-3
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,12 @@ template <typename Flavor> bool ECCVMVerifier_<Flavor>::verify_proof(const plonk
195195
// Construct batched commitment for NON-shifted polynomials
196196
size_t commitment_idx = 0;
197197
for (auto& commitment : commitments.get_unshifted()) {
198-
// TODO(@zac-williamson) ensure ECCVM polynomial commitments are never points at infinity (#2214)
198+
// TODO(@zac-williamson)(https://github.com/AztecProtocol/barretenberg/issues/820) ensure ECCVM polynomial
199+
// commitments are never points at infinity
199200
if (commitment.y != 0) {
200201
batched_commitment_unshifted += commitment * rhos[commitment_idx];
201202
} else {
202-
info("ECCVM Verifier: point at infinity (unshifted)");
203+
// TODO(https://github.com/AztecProtocol/barretenberg/issues/820)
203204
}
204205
++commitment_idx;
205206
}
@@ -210,7 +211,7 @@ template <typename Flavor> bool ECCVMVerifier_<Flavor>::verify_proof(const plonk
210211
if (commitment.y != 0) {
211212
batched_commitment_to_be_shifted += commitment * rhos[commitment_idx];
212213
} else {
213-
info("ECCVM Verifier: point at infinity (to be shifted)");
214+
// TODO(https://github.com/AztecProtocol/barretenberg/issues/820)
214215
}
215216
++commitment_idx;
216217
}

barretenberg/cpp/src/barretenberg/goblin/goblin.hpp

+14-18
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,11 @@ class Goblin {
169169
auto instance = composer.create_instance(circuit_builder);
170170
auto prover = composer.create_prover(instance);
171171
auto ultra_proof = prover.construct_proof();
172-
instance_inspector::inspect_instance(instance);
173172

174173
// TODO(https://github.com/AztecProtocol/barretenberg/issues/811): no merge prover for now since we're not
175174
// mocking the first set of ecc ops
176175
// // Construct and store the merge proof to be recursively verified on the next call to accumulate
177-
// info("create_merge_prover");
178176
// auto merge_prover = composer.create_merge_prover(op_queue);
179-
// info("merge_prover.construct_proof()");
180177
// merge_proof = merge_prover.construct_proof();
181178

182179
// if (!merge_proof_exists) {
@@ -190,7 +187,6 @@ class Goblin {
190187
// ACIRHACK
191188
Proof prove_for_acir()
192189
{
193-
info("Goblin.prove(): op_queue size = ", op_queue->ultra_ops[0].size());
194190
Proof proof;
195191

196192
proof.merge_proof = std::move(merge_proof);
@@ -216,9 +212,7 @@ class Goblin {
216212
{
217213
// ACIRHACK
218214
// MergeVerifier merge_verifier;
219-
// info("constructed merge_verifier");
220215
// bool merge_verified = merge_verifier.verify_proof(proof.merge_proof);
221-
// info("verified merge proof. result: ", merge_verified);
222216

223217
auto eccvm_verifier = eccvm_composer->create_verifier(*eccvm_builder);
224218
bool eccvm_verified = eccvm_verifier.verify_proof(proof.eccvm_proof);
@@ -235,17 +229,21 @@ class Goblin {
235229
// ACIRHACK
236230
std::vector<uint8_t> construct_proof(GoblinUltraCircuitBuilder& builder)
237231
{
238-
info("goblin: construct_proof");
232+
// Construct a GUH proof
239233
accumulate_for_acir(builder);
240-
info("accumulate complete.");
241-
std::vector<uint8_t> goblin_proof = prove_for_acir().to_buffer();
242-
std::vector<uint8_t> result(accumulator.proof.proof_data.size() + goblin_proof.size());
234+
235+
std::vector<uint8_t> result(accumulator.proof.proof_data.size());
243236

244237
const auto insert = [&result](const std::vector<uint8_t>& buf) {
245238
result.insert(result.end(), buf.begin(), buf.end());
246239
};
240+
247241
insert(accumulator.proof.proof_data);
248-
insert(goblin_proof);
242+
243+
// TODO(https://github.com/AztecProtocol/barretenberg/issues/819): Skip ECCVM/Translator proof for now
244+
// std::vector<uint8_t> goblin_proof = prove_for_acir().to_buffer();
245+
// insert(goblin_proof);
246+
249247
return result;
250248
}
251249

@@ -256,15 +254,13 @@ class Goblin {
256254
const auto extract_final_kernel_proof = [&]([[maybe_unused]] auto& input_proof) { return accumulator.proof; };
257255

258256
GoblinUltraVerifier verifier{ accumulator.verification_key };
259-
info("constructed GUH verifier");
260257
bool verified = verifier.verify_proof(extract_final_kernel_proof(proof));
261-
info(" verified GUH proof; result: ", verified);
262258

263-
const auto extract_goblin_proof = [&]([[maybe_unused]] auto& input_proof) { return proof_; };
264-
auto goblin_proof = extract_goblin_proof(proof);
265-
info("extracted goblin proof");
266-
verified = verified && verify_for_acir(goblin_proof);
267-
info("verified goblin proof");
259+
// TODO(https://github.com/AztecProtocol/barretenberg/issues/819): Skip ECCVM/Translator verification for now
260+
// const auto extract_goblin_proof = [&]([[maybe_unused]] auto& input_proof) { return proof_; };
261+
// auto goblin_proof = extract_goblin_proof(proof);
262+
// verified = verified && verify_for_acir(goblin_proof);
263+
268264
return verified;
269265
}
270266
};

barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,14 @@ template <typename FF> void GoblinUltraCircuitBuilder_<FF>::populate_ecc_op_wire
246246
num_ecc_op_gates += 2;
247247
};
248248

249+
template <typename FF> void GoblinUltraCircuitBuilder_<FF>::set_goblin_ecc_op_code_constant_variables()
250+
{
251+
null_op_idx = this->zero_idx;
252+
add_accum_op_idx = this->put_constant_variable(FF(EccOpCode::ADD_ACCUM));
253+
mul_accum_op_idx = this->put_constant_variable(FF(EccOpCode::MUL_ACCUM));
254+
equality_op_idx = this->put_constant_variable(FF(EccOpCode::EQUALITY));
255+
}
256+
249257
template <typename FF>
250258
void GoblinUltraCircuitBuilder_<FF>::create_poseidon2_external_gate(const poseidon2_external_gate_<FF>& in)
251259
{

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

+38-13
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
#pragma once
2-
#include "barretenberg/polynomials/polynomial.hpp"
32
#include "barretenberg/proof_system/arithmetization/arithmetization.hpp"
43
#include "barretenberg/proof_system/op_queue/ecc_op_queue.hpp"
5-
#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp"
6-
#include "barretenberg/proof_system/plookup_tables/types.hpp"
7-
#include "barretenberg/proof_system/types/merkle_hash_type.hpp"
8-
#include "barretenberg/proof_system/types/pedersen_commitment_type.hpp"
94
#include "ultra_circuit_builder.hpp"
10-
#include <optional>
115

126
namespace proof_system {
137

@@ -20,8 +14,6 @@ template <typename FF> class GoblinUltraCircuitBuilder_ : public UltraCircuitBui
2014
static constexpr size_t DEFAULT_NON_NATIVE_FIELD_LIMB_BITS =
2115
UltraCircuitBuilder_<arithmetization::UltraHonk<FF>>::DEFAULT_NON_NATIVE_FIELD_LIMB_BITS;
2216

23-
size_t num_vars_added_in_constructor = 0; // needed in constructing circuit from acir
24-
2517
size_t num_ecc_op_gates = 0; // number of ecc op "gates" (rows); these are placed at the start of the circuit
2618

2719
// Stores record of ecc operations and performs corresponding native operations internally
@@ -70,6 +62,7 @@ template <typename FF> class GoblinUltraCircuitBuilder_ : public UltraCircuitBui
7062
private:
7163
void populate_ecc_op_wires(const ecc_op_tuple& in);
7264
ecc_op_tuple decompose_ecc_operands(uint32_t op, const g1::affine_element& point, const FF& scalar = FF::zero());
65+
void set_goblin_ecc_op_code_constant_variables();
7366

7467
public:
7568
GoblinUltraCircuitBuilder_(const size_t size_hint = 0,
@@ -78,16 +71,48 @@ template <typename FF> class GoblinUltraCircuitBuilder_ : public UltraCircuitBui
7871
, op_queue(op_queue_in)
7972
{
8073
// Set indices to constants corresponding to Goblin ECC op codes
81-
null_op_idx = this->zero_idx;
82-
add_accum_op_idx = this->put_constant_variable(FF(EccOpCode::ADD_ACCUM));
83-
mul_accum_op_idx = this->put_constant_variable(FF(EccOpCode::MUL_ACCUM));
84-
equality_op_idx = this->put_constant_variable(FF(EccOpCode::EQUALITY));
85-
num_vars_added_in_constructor = this->variables.size();
74+
set_goblin_ecc_op_code_constant_variables();
8675
};
8776
GoblinUltraCircuitBuilder_(std::shared_ptr<ECCOpQueue> op_queue_in)
8877
: GoblinUltraCircuitBuilder_(0, op_queue_in)
8978
{}
9079

80+
/**
81+
* @brief Constructor from data generated from ACIR
82+
*
83+
* @param op_queue_in Op queue to which goblinized group ops will be added
84+
* @param witness_values witnesses values known to acir
85+
* @param public_inputs indices of public inputs in witness array
86+
* @param varnum number of known witness
87+
*
88+
* @note The size of witness_values may be less than varnum. The former is the set of actual witness values known at
89+
* the time of acir generation. The former may be larger and essentially acounts for placeholders for witnesses that
90+
* we know will exist but whose values are not known during acir generation. Both are in general less than the total
91+
* number of variables/witnesses that might be present for a circuit generated from acir, since many gates will
92+
* depend on the details of the bberg implementation (or more generally on the backend used to process acir).
93+
*/
94+
GoblinUltraCircuitBuilder_(std::shared_ptr<ECCOpQueue> op_queue_in,
95+
auto& witness_values,
96+
std::vector<uint32_t>& public_inputs,
97+
size_t varnum)
98+
: UltraCircuitBuilder_<arithmetization::UltraHonk<FF>>()
99+
, op_queue(op_queue_in)
100+
{
101+
// Add the witness variables known directly from acir
102+
for (size_t idx = 0; idx < varnum; ++idx) {
103+
// Zeros are added for variables whose existence is known but whose values are not yet known. The values may
104+
// be "set" later on via the assert_equal mechanism.
105+
auto value = idx < witness_values.size() ? witness_values[idx] : 0;
106+
this->add_variable(value);
107+
}
108+
109+
// Add the public_inputs from acir
110+
this->public_inputs = public_inputs;
111+
112+
// Set indices to constants corresponding to Goblin ECC op codes
113+
set_goblin_ecc_op_code_constant_variables();
114+
};
115+
91116
void finalize_circuit();
92117
void add_gates_to_ensure_all_polys_are_non_zero();
93118

0 commit comments

Comments
 (0)