Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
components_check.test.cpp
Go to the documentation of this file.
1
11
12#include <array>
13#include <gtest/gtest.h>
14#include <memory>
15#include <string>
16#include <unordered_map>
17#include <unordered_set>
18
19using namespace acir_format;
20
21namespace {
22
23using AcirComponentsCheckBuilder = UltraCircuitBuilder;
24
25Acir::Witness make_witness(uint32_t witness_idx)
26{
27 return Acir::Witness{ .value = witness_idx };
28}
29
30Acir::FunctionInput make_witness_input(uint32_t witness_idx)
31{
32 return Acir::FunctionInput{ .value =
33 Acir::FunctionInput::Witness{ .value = Acir::Witness{ .value = witness_idx } } };
34}
35
36Acir::FunctionInput make_constant_input(const bb::fr& value)
37{
39}
40
41Acir::Expression make_constant_expression(const bb::fr& value)
42{
43 return Acir::Expression{
44 .mul_terms = {},
45 .linear_combinations = {},
46 .q_c = value.to_buffer(),
47 };
48}
49
50Acir::Expression make_witness_expression(uint32_t witness_idx)
51{
52 return Acir::Expression{
53 .mul_terms = {},
54 .linear_combinations = { { bb::fr::one().to_buffer(), Acir::Witness{ .value = witness_idx } } },
55 .q_c = bb::fr::zero().to_buffer(),
56 };
57}
58
59Acir::Circuit make_circuit(std::vector<Acir::Opcode> opcodes)
60{
61 return Acir::Circuit{
62 .opcodes = std::move(opcodes),
63 .public_parameters = {},
64 .return_values = {},
65 };
66}
67
68class WitnessFactory {
69 public:
70 explicit WitnessFactory(uint32_t start = 0)
71 : next_(start)
72 {}
73
74 uint32_t next_index() { return next_++; }
75
76 Acir::Witness next_witness() { return make_witness(next_index()); }
77
78 Acir::FunctionInput next_input() { return make_witness_input(next_index()); }
79
80 std::vector<Acir::Witness> next_witnesses(size_t count)
81 {
82 std::vector<Acir::Witness> witnesses;
83 witnesses.reserve(count);
84 for (size_t i = 0; i < count; ++i) {
85 witnesses.push_back(next_witness());
86 }
87 return witnesses;
88 }
89
90 std::vector<Acir::FunctionInput> next_inputs(size_t count)
91 {
92 std::vector<Acir::FunctionInput> inputs;
93 inputs.reserve(count);
94 for (size_t i = 0; i < count; ++i) {
95 inputs.push_back(next_input());
96 }
97 return inputs;
98 }
99
100 template <size_t N> std::shared_ptr<std::array<Acir::FunctionInput, N>> next_input_array()
101 {
103 for (auto& input : *inputs) {
104 input = next_input();
105 }
106 return inputs;
107 }
108
109 template <size_t N> std::shared_ptr<std::array<Acir::Witness, N>> next_witness_array()
110 {
112 for (auto& output : *outputs) {
113 output = next_witness();
114 }
115 return outputs;
116 }
117
118 private:
119 uint32_t next_;
120};
121
122std::vector<acir_components_check::Error> run_components_check(const Acir::Circuit& circuit)
123{
125 AcirProgram program{ .constraints = constraints, .witness = {} };
126 auto builder = create_circuit<AcirComponentsCheckBuilder>(program);
128 return checker.check();
129}
130
131void expect_no_component_errors(const std::vector<acir_components_check::Error>& errors)
132{
133 if (errors.empty()) {
134 return;
135 }
136 std::string msg;
137 for (const auto& err : errors) {
138 msg += err.message;
139 msg += '\n';
140 }
141 FAIL() << msg;
142}
143
144void expect_single_error_type(const std::vector<acir_components_check::Error>& errors,
146{
147 ASSERT_EQ(errors.size(), 1U);
148 EXPECT_EQ(errors[0].type, type);
149}
150
151size_t count_acir_components_for_witnesses(const Acir::Circuit& circuit, const std::vector<uint32_t>& witnesses)
152{
154 graph.process_acir_circuit(circuit);
155 auto witness_to_component = graph.get_witness_component_map();
156
157 std::unordered_set<size_t> components;
158 for (auto witness : witnesses) {
159 auto it = witness_to_component.find(witness);
160 if (it != witness_to_component.end()) {
161 components.insert(it->second);
162 }
163 }
164 return components.size();
165}
166
167size_t count_circuit_components_for_witnesses(const Acir::Circuit& circuit, const std::vector<uint32_t>& witnesses)
168{
170 AcirProgram program{ .constraints = constraints, .witness = {} };
171 auto builder = create_circuit<AcirComponentsCheckBuilder>(program);
172
174 auto connected_components = analyzer.find_connected_components();
175
176 std::unordered_map<uint32_t, size_t> real_variable_to_component;
177 for (size_t component_id = 0; component_id < connected_components.size(); ++component_id) {
178 for (auto real_var : connected_components[component_id].vars()) {
179 real_variable_to_component[real_var] = component_id;
180 }
181 }
182
183 std::unordered_set<size_t> components;
184 for (auto witness : witnesses) {
185 if (witness >= builder.real_variable_index.size()) {
186 continue;
187 }
188 auto real_var = builder.real_variable_index[witness];
189 auto it = real_variable_to_component.find(real_var);
190 if (it != real_variable_to_component.end()) {
191 components.insert(it->second);
192 }
193 }
194 return components.size();
195}
196
197} // namespace
198
199class AcirComponentsCheckTest : public ::testing::Test {
200 protected:
202};
203
204TEST_F(AcirComponentsCheckTest, SingleLinearConstraintLinksTwoWitnesses)
205{
207 { bb::fr(-1).to_buffer(), Acir::Witness{ 1 } } },
208 .q_c = bb::fr::zero().to_buffer() };
209 Acir::Circuit circuit{
211 .public_parameters = {},
212 .return_values = {},
213 };
214
215 expect_no_component_errors(run_components_check(circuit));
216}
217
218TEST_F(AcirComponentsCheckTest, AllBlackBoxFunctionOpcodesPassComponentsCheck)
219{
220 WitnessFactory witnesses;
221
222 auto sha256_inputs = witnesses.next_input_array<16>();
223 auto sha256_hash_values = witnesses.next_input_array<8>();
224 auto sha256_outputs = witnesses.next_witness_array<8>();
225
226 auto k1_public_key_x = witnesses.next_input_array<32>();
227 auto k1_public_key_y = witnesses.next_input_array<32>();
228 auto k1_signature = witnesses.next_input_array<64>();
229 auto k1_hashed_message = witnesses.next_input_array<32>();
230
231 auto r1_public_key_x = witnesses.next_input_array<32>();
232 auto r1_public_key_y = witnesses.next_input_array<32>();
233 auto r1_signature = witnesses.next_input_array<64>();
234 auto r1_hashed_message = witnesses.next_input_array<32>();
235
236 auto msm_outputs = witnesses.next_witness_array<2>();
237 auto ec_add_input1 = witnesses.next_input_array<2>();
238 auto ec_add_input2 = witnesses.next_input_array<2>();
239 auto ec_add_outputs = witnesses.next_witness_array<2>();
240 auto keccak_inputs = witnesses.next_input_array<25>();
241 auto keccak_outputs = witnesses.next_witness_array<25>();
242
243 Acir::Circuit circuit = make_circuit({
248 .lhs = witnesses.next_input(),
249 .rhs = witnesses.next_input(),
250 .num_bits = 8,
251 .output = witnesses.next_witness(),
252 } } } },
254 .value =
257 .lhs = witnesses.next_input(),
258 .rhs = witnesses.next_input(),
259 .num_bits = 8,
260 .output =
261 witnesses.next_witness(),
262 } } } },
264 .value =
267 .input =
268 witnesses.next_input(),
269 .num_bits = 16,
270 } } } },
273 .value =
275 .value =
277 .inputs =
278 witnesses.next_inputs(16),
279 .iv =
280 witnesses.next_input_array<16>(),
281 .key = witnesses.next_input_array<16>(),
282 .outputs =
283 witnesses.next_witnesses(16),
284 } } } },
286 .value =
288 .value =
290 .value =
292 .inputs =
293 sha256_inputs,
294 .hash_values = sha256_hash_values,
295 .outputs = sha256_outputs,
296 } } } },
299 .value =
301 .value =
303 .inputs =
304 witnesses.next_inputs(64),
305 .outputs =
306 witnesses.next_witness_array<32>(),
307 } } } },
310 .value =
312 .value =
314 .inputs =
315 witnesses.next_inputs(64),
316 .outputs =
317 witnesses.next_witness_array<32>(),
318 } } } },
319 Acir::
320 Opcode{ .value =
323 .value =
325 .public_key_x = k1_public_key_x,
326 .public_key_y = k1_public_key_y,
327 .signature = k1_signature,
328 .hashed_message = k1_hashed_message,
329 .predicate = make_constant_input(bb::fr::one()),
330 .output = witnesses.next_witness(),
331 } } } },
332 Acir::
333 Opcode{ .value =
336 .value =
338 .public_key_x = r1_public_key_x,
339 .public_key_y = r1_public_key_y,
340 .signature = r1_signature,
341 .hashed_message = r1_hashed_message,
342 .predicate = make_constant_input(bb::fr::one()),
343 .output = witnesses.next_witness(),
344 } } } },
345 Acir::
346 Opcode{ .value =
349 .value =
351 .points = witnesses.next_inputs(2),
352 .scalars = witnesses.next_inputs(2),
353 .predicate = make_constant_input(bb::fr::one()),
354 .outputs = msm_outputs,
355 } } } },
356 Acir::
357 Opcode{ .value =
360 .value =
362 .input1 = ec_add_input1,
363 .input2 = ec_add_input2,
364 .predicate = make_constant_input(bb::fr::one()),
365 .outputs = ec_add_outputs,
366 } } } },
367 Acir::Opcode{ .value = Acir::Opcode::
368 BlackBoxFuncCall{ .value = Acir::BlackBoxFuncCall{ .value =
370 .inputs = keccak_inputs,
371 .outputs = keccak_outputs,
372 } } } },
377 .verification_key = witnesses.next_inputs(4),
378 .proof = witnesses.next_inputs(8),
379 .public_inputs = witnesses.next_inputs(2),
380 .key_hash = witnesses.next_input(),
381 .proof_type = 0,
382 .predicate = make_constant_input(bb::fr::zero()),
383 } } } },
388 .inputs = witnesses.next_inputs(4),
389 .outputs = witnesses.next_witnesses(4),
390 } } } },
391 });
392
393 expect_no_component_errors(run_components_check(circuit));
394}
395
396TEST_F(AcirComponentsCheckTest, FixedBaseMultiScalarMulMergesCircuitComponents)
397{
398 WitnessFactory witnesses;
399 const auto generator_x = bb::grumpkin::g1::affine_one.x;
400 const auto generator_y = bb::grumpkin::g1::affine_one.y;
401
402 Acir::Circuit circuit =
403 make_circuit(
404 {
406 .value =
409 .value =
411 .points = { make_constant_input(generator_x),
412 make_constant_input(generator_y) },
413 .scalars = witnesses.next_inputs(2),
414 .predicate = make_constant_input(bb::fr::one()),
415 .outputs = witnesses.next_witness_array<2>(),
416 } } } },
418 .value =
420 .value =
422 .value =
424 .points = { make_constant_input(generator_x),
425 make_constant_input(generator_y) },
426 .scalars = witnesses.next_inputs(2),
427 .predicate = make_constant_input(bb::fr::one()),
428 .outputs = witnesses.next_witness_array<2>(),
429 } } } },
430 });
431
432 const std::vector<uint32_t> relevant_witnesses = { 0, 1, 2, 3, 5, 6, 7, 8 };
433
434 EXPECT_EQ(count_acir_components_for_witnesses(circuit, relevant_witnesses), 2U);
435 EXPECT_EQ(count_circuit_components_for_witnesses(circuit, relevant_witnesses), 1U);
436}
437
438TEST_F(AcirComponentsCheckTest, MemoryOpcodesPassComponentsCheck)
439{
440 Acir::Circuit circuit =
441 make_circuit(
442 {
445 .block_id = Acir::BlockId{ .value = 0 },
446 .init = { make_witness(0), make_witness(1) },
447 .block_type = Acir::BlockType{ .value = Acir::BlockType::Memory{} },
448 } },
451 .block_id = Acir::BlockId{ .value = 0 },
452 .op =
454 .read = false,
455 .index = make_witness(2),
456 .value = make_witness(3),
457 },
458 } },
459 });
460
461 expect_no_component_errors(run_components_check(circuit));
462}
463
464TEST_F(AcirComponentsCheckTest, BrilligCallProducesNoComponentErrors)
465{
466 Acir::Circuit circuit = make_circuit({
468 .id = 7,
469 .inputs =
470 {
472 .value = Acir::BrilligInputs::Single{ .value = make_witness_expression(0) } },
474 .value = { make_witness_expression(1),
475 make_witness_expression(2) } } },
478 },
479 .outputs =
480 {
482 .value = Acir::BrilligOutputs::Simple{ .value = make_witness(3) } },
484 .value = Acir::BrilligOutputs::Array{ .value = { make_witness(4),
485 make_witness(5) } } },
486 },
487 .predicate = make_constant_expression(bb::fr::one()),
488 } },
489 });
490
491 expect_no_component_errors(run_components_check(circuit));
492}
493
494TEST_F(AcirComponentsCheckTest, CallOpcodeIsRejectedByCircuitCreation)
495{
496 Acir::Circuit circuit = make_circuit({
499 .id = 9,
500 .inputs = { make_witness(0), make_witness(1) },
501 .outputs = { make_witness(2) },
502 .predicate = make_constant_expression(bb::fr::one()),
503 } },
504 });
505
506 EXPECT_THROW_WITH_MESSAGE(run_components_check(circuit), "Call opcode is not supported");
507}
508
509TEST_F(AcirComponentsCheckTest, DetectsSplitComponents)
510{
511 Acir::Circuit circuit = make_circuit({
515 {
516 { bb::fr::one().to_buffer(), make_witness(0) },
517 { bb::fr::one().to_buffer(), make_witness(1) },
518 { bb::fr::one().to_buffer(), make_witness(2) },
519 { bb::fr(-3).to_buffer(), make_witness(3) },
520 },
521 .q_c = bb::fr::zero().to_buffer(),
522 } } },
526 {
527 { bb::fr::one().to_buffer(), make_witness(4) },
528 { bb::fr(-1).to_buffer(), make_witness(5) },
529 },
530 .q_c = bb::fr::zero().to_buffer(),
531 } } },
532 });
533
535 AcirProgram program{ .constraints = constraints, .witness = {} };
536 auto builder = create_circuit<AcirComponentsCheckBuilder>(program);
537 // Corrupt the circuit
538 builder.real_variable_index[2] = builder.zero_idx();
539
541 auto errors = checker.check();
542 expect_single_error_type(errors, acir_components_check::Error::Type::SPLIT);
543}
544
545TEST_F(AcirComponentsCheckTest, DetectsUnconstrainedWitnesses)
546{
547 Acir::Circuit circuit = make_circuit({
551 {
552 { bb::fr::one().to_buffer(), make_witness(8) },
553 { bb::fr(-1).to_buffer(), make_witness(9) },
554 },
555 .q_c = bb::fr::zero().to_buffer(),
556 } } },
557 });
558
560 AcirProgram program{ .constraints = constraints, .witness = {} };
561 auto builder = create_circuit<AcirComponentsCheckBuilder>(program);
562 // Corrupt the circuit
563 builder.real_variable_index.resize(9);
564
566 auto errors = checker.check();
567 expect_single_error_type(errors, acir_components_check::Error::Type::UNCONSTRAINED);
568}
569
570TEST_F(AcirComponentsCheckTest, TwoIndependentLinkedPairs)
571{
573 { bb::fr(-1).to_buffer(), Acir::Witness{ 1 } } },
574 .q_c = bb::fr::zero().to_buffer() };
576 { bb::fr(-1).to_buffer(), Acir::Witness{ 3 } } },
577 .q_c = bb::fr::zero().to_buffer() };
578 Acir::Circuit circuit{
581 .public_parameters = {},
582 .return_values = {},
583 };
584
585 expect_no_component_errors(run_components_check(circuit));
586}
587
588TEST_F(AcirComponentsCheckTest, PublicInputStyleCircuit)
589{
590 // Mirrors the structure of AcirFormatTests.PublicInputs: two linked witnesses plus public metadata.
592 { bb::fr(-1).to_buffer(), Acir::Witness{ 2 } } },
593 .q_c = bb::fr(-2).to_buffer() };
594 Acir::Circuit circuit{
596 .public_parameters =
598 .return_values = Acir::PublicInputs{ .value = { Acir::Witness{ .value = 4 }, Acir::Witness{ .value = 5 } } },
599 };
600
601 expect_no_component_errors(run_components_check(circuit));
602}
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:224
Undirected graph on ACIR witness indices; connected components = "ACIR components".
std::unordered_map< uint32_t, size_t > get_witness_component_map() const
Map each witness that appears in at least one edge to a component id.
void process_acir_circuit(const Acir::Circuit &circuit)
Walk circuit.opcodes, populate adjacency, then merge per-block memory witnesses.
Structural comparison between ACIR-level and circuit-level connected components.
std::vector< Error > check()
Run the full check. Returns list of errors (empty = pass).
static constexpr affine_element affine_one
Definition group.hpp:50
TEST_F(AcirComponentsCheckTest, SingleLinearConstraintLinksTwoWitnesses)
AluTraceBuilder builder
Definition alu.test.cpp:124
AvmProvingInputs inputs
AcirFormat circuit_serde_to_acir_format(Acir::Circuit const &circuit, bool is_mega)
Convert an Acir::Circuit into an AcirFormat by processing all the opcodes.
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
field< Bn254FrParams > fr
Definition fr.hpp:155
UltraCircuitBuilder_< UltraExecutionTraceBlocks > UltraCircuitBuilder
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
bb::VectorAffineElementPushSpan< BaseParams > rhs
std::vector< Acir::FunctionInput > inputs
Definition acir.hpp:4413
Acir::FunctionInput lhs
Definition acir.hpp:4475
std::vector< Acir::FunctionInput > inputs
Definition acir.hpp:4647
std::vector< Acir::FunctionInput > inputs
Definition acir.hpp:4695
std::shared_ptr< std::array< Acir::FunctionInput, 32 > > public_key_x
Definition acir.hpp:4743
std::shared_ptr< std::array< Acir::FunctionInput, 32 > > public_key_x
Definition acir.hpp:4819
std::shared_ptr< std::array< Acir::FunctionInput, 2 > > input1
Definition acir.hpp:4957
std::shared_ptr< std::array< Acir::FunctionInput, 25 > > inputs
Definition acir.hpp:5019
std::vector< Acir::FunctionInput > points
Definition acir.hpp:4895
std::vector< Acir::FunctionInput > inputs
Definition acir.hpp:5143
Acir::FunctionInput input
Definition acir.hpp:4599
std::vector< Acir::FunctionInput > verification_key
Definition acir.hpp:5067
std::shared_ptr< std::array< Acir::FunctionInput, 16 > > inputs
Definition acir.hpp:5191
Acir::FunctionInput lhs
Definition acir.hpp:4537
std::variant< AES128Encrypt, AND, XOR, RANGE, Blake2s, Blake3, EcdsaSecp256k1, EcdsaSecp256r1, MultiScalarMul, EmbeddedCurveAdd, Keccakf1600, RecursiveAggregation, Poseidon2Permutation, Sha256Compression > value
Definition acir.hpp:5259
uint32_t value
Definition acir.hpp:5682
std::variant< Memory, CallData, ReturnData > value
Definition acir.hpp:5733
std::vector< Acir::Expression > value
Definition acir.hpp:5930
Acir::Expression value
Definition acir.hpp:5912
std::variant< Single, Array, MemoryArray > value
Definition acir.hpp:5965
std::vector< Acir::Witness > value
Definition acir.hpp:6133
std::variant< Simple, Array > value
Definition acir.hpp:6150
std::vector< Acir::Opcode > opcodes
Definition acir.hpp:7232
std::vector< std::tuple< std::vector< uint8_t >, Acir::Witness > > linear_combinations
Definition acir.hpp:5856
std::vector< std::tuple< std::vector< uint8_t >, Acir::Witness, Acir::Witness > > mul_terms
Definition acir.hpp:5855
std::vector< uint8_t > value
Definition acir.hpp:4253
std::variant< Constant, Witness > value
Definition acir.hpp:4288
bool read
Definition acir.hpp:6273
Acir::Expression value
Definition acir.hpp:6330
Acir::BlackBoxFuncCall value
Definition acir.hpp:6348
Acir::BlockId block_id
Definition acir.hpp:6414
Acir::BlockId block_id
Definition acir.hpp:6366
std::variant< AssertZero, BlackBoxFuncCall, MemoryOp, MemoryInit, BrilligCall, Call > value
Definition acir.hpp:6592
std::vector< Acir::Witness > value
Definition acir.hpp:7213
uint32_t value
Definition acir.hpp:4233
Struct containing both the constraints to be added to the circuit and the witness vector.
static constexpr field one()
BB_INLINE std::vector< uint8_t > to_buffer() const
static constexpr field zero()