Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ultra_circuit_builder.test.cpp
Go to the documentation of this file.
8
9#include <cstddef>
10#include <gtest/gtest.h>
11
12using namespace bb;
13
14namespace {
16}
17namespace bb {
18
19TEST(UltraCircuitBuilder, CopyConstructor)
20{
22
23 for (size_t i = 0; i < 16; ++i) {
24 for (size_t j = 0; j < 16; ++j) {
25 uint64_t left = static_cast<uint64_t>(j);
26 uint64_t right = static_cast<uint64_t>(i);
27 uint32_t left_idx = builder.add_variable(fr(left));
28 uint32_t right_idx = builder.add_variable(fr(right));
29 uint32_t result_idx = builder.add_variable(fr(left ^ right));
30
31 uint32_t add_idx = builder.add_variable(fr(left) + fr(right) + builder.get_variable(result_idx));
32 builder.create_big_add_gate(
33 { left_idx, right_idx, result_idx, add_idx, fr(1), fr(1), fr(1), fr(-1), fr(0) });
34 }
35 }
36
37 bool result = CircuitChecker::check(builder);
38 EXPECT_EQ(result, true);
39
40 UltraCircuitBuilder duplicate_builder{ builder };
41
42 EXPECT_EQ(duplicate_builder.get_estimated_num_finalized_gates(), builder.get_estimated_num_finalized_gates());
43 EXPECT_TRUE(CircuitChecker::check(duplicate_builder));
44}
45
46TEST(UltraCircuitBuilder, CreateGatesFromPlookupAccumulators)
47{
48
49 UltraCircuitBuilder circuit_builder = UltraCircuitBuilder();
50
51 fr input_value = fr::random_element();
52 const fr input_lo = static_cast<uint256_t>(input_value).slice(0, plookup::fixed_base::table::BITS_PER_LO_SCALAR);
53 const auto input_lo_index = circuit_builder.add_variable(input_lo);
54
56
57 const auto lookup_witnesses = circuit_builder.create_gates_from_plookup_accumulators(
58 plookup::MultiTableId::FIXED_BASE_LEFT_LO, sequence_data_lo, input_lo_index);
59
61
62 EXPECT_EQ(num_lookups, lookup_witnesses[plookup::ColumnIdx::C1].size());
63
64 {
66
68 std::vector<uint8_t> input_buf;
69 write(input_buf, base_point);
70 const auto offset_generators =
72
73 grumpkin::g1::element accumulator = base_point;
74 uint256_t expected_scalar(input_lo);
75 const auto table_bits = plookup::fixed_base::table::BITS_PER_TABLE;
77 for (size_t i = 0; i < num_tables; ++i) {
78
79 auto round_scalar = circuit_builder.get_variable(lookup_witnesses[plookup::ColumnIdx::C1][i]);
80 auto round_x = circuit_builder.get_variable(lookup_witnesses[plookup::ColumnIdx::C2][i]);
81 auto round_y = circuit_builder.get_variable(lookup_witnesses[plookup::ColumnIdx::C3][i]);
82
83 EXPECT_EQ(uint256_t(round_scalar), expected_scalar);
84
85 auto next_scalar = static_cast<uint256_t>(
86 (i == num_tables - 1) ? fr(0)
87 : circuit_builder.get_variable(lookup_witnesses[plookup::ColumnIdx::C1][i + 1]));
88
89 uint256_t slice = static_cast<uint256_t>(round_scalar) - (next_scalar << table_bits);
90 EXPECT_EQ(slice, (uint256_t(input_lo) >> (i * table_bits)) & mask);
91
92 grumpkin::g1::affine_element expected_point(accumulator * static_cast<uint256_t>(slice) +
93 offset_generators[i]);
94
95 EXPECT_EQ(round_x, expected_point.x);
96 EXPECT_EQ(round_y, expected_point.y);
97 for (size_t j = 0; j < table_bits; ++j) {
98 accumulator = accumulator.dbl();
99 }
100 expected_scalar >>= table_bits;
101 }
102 }
103
104 bool result = CircuitChecker::check(circuit_builder);
105 EXPECT_EQ(result, true);
106}
107
108TEST(UltraCircuitBuilder, BadLookupFailure)
109{
112
113 // Erroneously set a non-zero wire value to zero in one of the lookup gates
114 for (auto& wire_3_witness_idx : builder.blocks.lookup.w_o()) {
115 if (wire_3_witness_idx != builder.zero_idx()) {
116 wire_3_witness_idx = builder.zero_idx();
117 break;
118 }
119 }
120
121 EXPECT_FALSE(CircuitChecker::check(builder));
122}
123
125{
127 fr a = fr::one();
128 builder.add_public_variable(a);
129 bool result = CircuitChecker::check(builder);
130 EXPECT_EQ(result, true);
131}
132TEST(UltraCircuitBuilder, TestNoLookupProof)
133{
135
136 for (size_t i = 0; i < 16; ++i) {
137 for (size_t j = 0; j < 16; ++j) {
138 uint64_t left = static_cast<uint64_t>(j);
139 uint64_t right = static_cast<uint64_t>(i);
140 uint32_t left_idx = builder.add_variable(fr(left));
141 uint32_t right_idx = builder.add_variable(fr(right));
142 uint32_t result_idx = builder.add_variable(fr(left ^ right));
143
144 uint32_t add_idx = builder.add_variable(fr(left) + fr(right) + builder.get_variable(result_idx));
145 builder.create_big_add_gate(
146 { left_idx, right_idx, result_idx, add_idx, fr(1), fr(1), fr(1), fr(-1), fr(0) });
147 }
148 }
149
150 bool result = CircuitChecker::check(builder);
151 EXPECT_EQ(result, true);
152}
153
154TEST(UltraCircuitBuilder, TestEllipticGate)
155{
157 typedef grumpkin::g1::element element;
159
161
163 affine_element p3(element(p1) + element(p2));
164
165 uint32_t x1 = builder.add_variable(p1.x);
166 uint32_t y1 = builder.add_variable(p1.y);
167 uint32_t x2 = builder.add_variable(p2.x);
168 uint32_t y2 = builder.add_variable(p2.y);
169 uint32_t x3 = builder.add_variable(p3.x);
170 uint32_t y3 = builder.add_variable(p3.y);
171
172 builder.create_ecc_add_gate({ x1, y1, x2, y2, x3, y3, 1 });
173
174 bool result = CircuitChecker::check(builder);
175 EXPECT_EQ(result, true);
176
177 builder.create_ecc_add_gate({ x1 + 1, y1, x2, y2, x3, y3, 1 });
178
179 EXPECT_EQ(CircuitChecker::check(builder), false);
180}
181
182TEST(UltraCircuitBuilder, TestEllipticGateFailure)
183{
185 typedef grumpkin::g1::element element;
187
188 // Create two valid points on the curve
191
192 // Compute the correct sum
193 affine_element p3_correct(element(p1) + element(p2));
194
195 // Create a point not on the curve by modifying p2's x-coordinate
196 bb::fr invalid_x = p2.x + bb::fr(1);
197
198 uint32_t x1 = builder.add_variable(p1.x);
199 uint32_t y1 = builder.add_variable(p1.y);
200 uint32_t x2_invalid = builder.add_variable(invalid_x); // Invalid x coordinate
201 uint32_t y2 = builder.add_variable(p2.y);
202 uint32_t x3 = builder.add_variable(p3_correct.x);
203 uint32_t y3 = builder.add_variable(p3_correct.y);
204
205 // Construct addition gate with a point not on the curve
206 builder.create_ecc_add_gate({ x1, y1, x2_invalid, y2, x3, y3, 1 });
207
208 // CircuitChecker should fail in the elliptic relation
209 bool result = CircuitChecker::check(builder);
210 EXPECT_EQ(result, false);
211}
212
213TEST(UltraCircuitBuilder, TestEllipticDoubleGate)
214{
216 typedef grumpkin::g1::element element;
218
220 affine_element p3(element(p1).dbl());
221
222 uint32_t x1 = builder.add_variable(p1.x);
223 uint32_t y1 = builder.add_variable(p1.y);
224 uint32_t x3 = builder.add_variable(p3.x);
225 uint32_t y3 = builder.add_variable(p3.y);
226
227 builder.create_ecc_dbl_gate({ x1, y1, x3, y3 });
228
229 bool result = CircuitChecker::check(builder);
230 EXPECT_EQ(result, true);
231}
232
233TEST(UltraCircuitBuilder, NonTrivialTagPermutation)
234{
237 fr b = -a;
238
239 auto a_idx = builder.add_variable(a);
240 auto b_idx = builder.add_variable(b);
241 auto c_idx = builder.add_variable(b);
242 auto d_idx = builder.add_variable(a);
243
244 builder.create_add_gate({ a_idx, b_idx, builder.zero_idx(), fr::one(), fr::one(), fr::zero(), fr::zero() });
245 builder.create_add_gate({ c_idx, d_idx, builder.zero_idx(), fr::one(), fr::one(), fr::zero(), fr::zero() });
246
247 builder.create_tag(1, 2);
248 builder.create_tag(2, 1);
249
250 builder.assign_tag(a_idx, 1);
251 builder.assign_tag(b_idx, 1);
252 builder.assign_tag(c_idx, 2);
253 builder.assign_tag(d_idx, 2);
254
255 bool result = CircuitChecker::check(builder);
256 EXPECT_EQ(result, true);
257
258 // Break the tag
259 builder.real_variable_tags[builder.real_variable_index[a_idx]] = 2;
260 EXPECT_EQ(CircuitChecker::check(builder), false);
261}
262
263TEST(UltraCircuitBuilder, NonTrivialTagPermutationAndCycles)
264{
267 fr c = -a;
268
269 auto a_idx = builder.add_variable(a);
270 auto b_idx = builder.add_variable(a);
271 builder.assert_equal(a_idx, b_idx);
272 auto c_idx = builder.add_variable(c);
273 auto d_idx = builder.add_variable(c);
274 builder.assert_equal(c_idx, d_idx);
275 auto e_idx = builder.add_variable(a);
276 auto f_idx = builder.add_variable(a);
277 builder.assert_equal(e_idx, f_idx);
278 auto g_idx = builder.add_variable(c);
279 auto h_idx = builder.add_variable(c);
280 builder.assert_equal(g_idx, h_idx);
281
282 builder.create_tag(1, 2);
283 builder.create_tag(2, 1);
284
285 builder.assign_tag(a_idx, 1);
286 builder.assign_tag(c_idx, 1);
287 builder.assign_tag(e_idx, 2);
288 builder.assign_tag(g_idx, 2);
289
290 builder.create_add_gate({ b_idx, a_idx, builder.zero_idx(), fr::one(), fr::neg_one(), fr::zero(), fr::zero() });
291 builder.create_add_gate({ c_idx, g_idx, builder.zero_idx(), fr::one(), -fr::one(), fr::zero(), fr::zero() });
292 builder.create_add_gate({ e_idx, f_idx, builder.zero_idx(), fr::one(), -fr::one(), fr::zero(), fr::zero() });
293
294 bool result = CircuitChecker::check(builder);
295 EXPECT_EQ(result, true);
296
297 // Break the tag
298 builder.real_variable_tags[builder.real_variable_index[a_idx]] = 2;
299 EXPECT_EQ(CircuitChecker::check(builder), false);
300}
301TEST(UltraCircuitBuilder, BadTagPermutation)
302{
305 fr b = -a;
306
307 auto a_idx = builder.add_variable(a);
308 auto b_idx = builder.add_variable(b);
309 auto c_idx = builder.add_variable(b);
310 auto d_idx = builder.add_variable(a + 1);
311
312 builder.create_add_gate({ a_idx, b_idx, builder.zero_idx(), 1, 1, 0, 0 });
313 builder.create_add_gate({ c_idx, d_idx, builder.zero_idx(), 1, 1, 0, -1 });
314
315 bool result = CircuitChecker::check(builder);
316 EXPECT_EQ(result, true);
317
318 builder.create_tag(1, 2);
319 builder.create_tag(2, 1);
320
321 builder.assign_tag(a_idx, 1);
322 builder.assign_tag(b_idx, 1);
323 builder.assign_tag(c_idx, 2);
324 builder.assign_tag(d_idx, 2);
325
327 EXPECT_EQ(result, false);
328}
329
331{
333 fr a = fr::one();
334 fr b = fr(2);
335 fr c = fr(3);
336 fr d = fr(4);
337
338 auto a_idx = builder.add_variable(a);
339 auto b_idx = builder.add_variable(b);
340 auto c_idx = builder.add_variable(c);
341 auto d_idx = builder.add_variable(d);
342 builder.create_sort_constraint({ a_idx, b_idx, c_idx, d_idx });
343
344 bool result = CircuitChecker::check(builder);
345 EXPECT_EQ(result, true);
346}
347
348std::vector<uint32_t> add_variables(UltraCircuitBuilder& builder, std::vector<fr> variables)
349{
350 std::vector<uint32_t> res;
351 for (size_t i = 0; i < variables.size(); i++) {
352 res.emplace_back(builder.add_variable(variables[i]));
353 }
354 return res;
355}
356TEST(UltraCircuitBuilder, SortWithEdgesGate)
357{
358 fr a = fr::one();
359 fr b = fr(2);
360 fr c = fr(3);
361 fr d = fr(4);
362 fr e = fr(5);
363 fr f = fr(6);
364 fr g = fr(7);
365 fr h = fr(8);
366
367 {
369 auto a_idx = builder.add_variable(a);
370 auto b_idx = builder.add_variable(b);
371 auto c_idx = builder.add_variable(c);
372 auto d_idx = builder.add_variable(d);
373 auto e_idx = builder.add_variable(e);
374 auto f_idx = builder.add_variable(f);
375 auto g_idx = builder.add_variable(g);
376 auto h_idx = builder.add_variable(h);
377 builder.create_sort_constraint_with_edges({ a_idx, b_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, a, h);
378 bool result = CircuitChecker::check(builder);
379 EXPECT_EQ(result, true);
380 }
381
382 {
384 auto a_idx = builder.add_variable(a);
385 auto b_idx = builder.add_variable(b);
386 auto c_idx = builder.add_variable(c);
387 auto d_idx = builder.add_variable(d);
388 auto e_idx = builder.add_variable(e);
389 auto f_idx = builder.add_variable(f);
390 auto g_idx = builder.add_variable(g);
391 auto h_idx = builder.add_variable(h);
392 builder.create_sort_constraint_with_edges({ a_idx, b_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, a, g);
393
394 bool result = CircuitChecker::check(builder);
395 EXPECT_EQ(result, false);
396 }
397 {
399 auto a_idx = builder.add_variable(a);
400 auto b_idx = builder.add_variable(b);
401 auto c_idx = builder.add_variable(c);
402 auto d_idx = builder.add_variable(d);
403 auto e_idx = builder.add_variable(e);
404 auto f_idx = builder.add_variable(f);
405 auto g_idx = builder.add_variable(g);
406 auto h_idx = builder.add_variable(h);
407 builder.create_sort_constraint_with_edges({ a_idx, b_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, b, h);
408
409 bool result = CircuitChecker::check(builder);
410 EXPECT_EQ(result, false);
411 }
412 {
414 auto a_idx = builder.add_variable(a);
415 auto c_idx = builder.add_variable(c);
416 auto d_idx = builder.add_variable(d);
417 auto e_idx = builder.add_variable(e);
418 auto f_idx = builder.add_variable(f);
419 auto g_idx = builder.add_variable(g);
420 auto h_idx = builder.add_variable(h);
421 auto b2_idx = builder.add_variable(fr(15));
422 builder.create_sort_constraint_with_edges({ a_idx, b2_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, b, h);
423 bool result = CircuitChecker::check(builder);
424 EXPECT_EQ(result, false);
425 }
426 {
428 auto idx = add_variables(builder, { 1, 2, 5, 6, 7, 10, 11, 13, 16, 17, 20, 22, 22, 25,
429 26, 29, 29, 32, 32, 33, 35, 38, 39, 39, 42, 42, 43, 45 });
430 builder.create_sort_constraint_with_edges(idx, 1, 45);
431 bool result = CircuitChecker::check(builder);
432 EXPECT_EQ(result, true);
433 }
434 {
436 auto idx = add_variables(builder, { 1, 2, 5, 6, 7, 10, 11, 13, 16, 17, 20, 22, 22, 25,
437 26, 29, 29, 32, 32, 33, 35, 38, 39, 39, 42, 42, 43, 45 });
438
439 builder.create_sort_constraint_with_edges(idx, 1, 29);
440 bool result = CircuitChecker::check(builder);
441 EXPECT_EQ(result, false);
442 }
443}
444
445TEST(UltraCircuitBuilder, RangeConstraint)
446{
447 {
449 auto indices = add_variables(builder, { 1, 2, 3, 4, 5, 6, 7, 8 });
450 for (size_t i = 0; i < indices.size(); i++) {
451 builder.create_new_range_constraint(indices[i], 8);
452 }
453 // auto ind = {a_idx,b_idx,c_idx,d_idx,e_idx,f_idx,g_idx,h_idx};
454 builder.create_sort_constraint(indices);
455 bool result = CircuitChecker::check(builder);
456 EXPECT_EQ(result, true);
457 }
458 {
460 auto indices = add_variables(builder, { 3 });
461 for (size_t i = 0; i < indices.size(); i++) {
462 builder.create_new_range_constraint(indices[i], 3);
463 }
464 // auto ind = {a_idx,b_idx,c_idx,d_idx,e_idx,f_idx,g_idx,h_idx};
465 builder.create_unconstrained_gates(indices);
466 bool result = CircuitChecker::check(builder);
467 EXPECT_EQ(result, true);
468 }
469 {
471 auto indices = add_variables(builder, { 1, 2, 3, 4, 5, 6, 8, 25 });
472 for (size_t i = 0; i < indices.size(); i++) {
473 builder.create_new_range_constraint(indices[i], 8);
474 }
475 builder.create_sort_constraint(indices);
476 bool result = CircuitChecker::check(builder);
477 EXPECT_EQ(result, false);
478 }
479 {
481 auto indices =
482 add_variables(builder, { 1, 2, 3, 4, 5, 6, 10, 8, 15, 11, 32, 21, 42, 79, 16, 10, 3, 26, 19, 51 });
483 for (size_t i = 0; i < indices.size(); i++) {
484 builder.create_new_range_constraint(indices[i], 128);
485 }
486 builder.create_unconstrained_gates(indices);
487 bool result = CircuitChecker::check(builder);
488 EXPECT_EQ(result, true);
489 }
490 {
492 auto indices =
493 add_variables(builder, { 1, 2, 3, 80, 5, 6, 29, 8, 15, 11, 32, 21, 42, 79, 16, 10, 3, 26, 13, 14 });
494 for (size_t i = 0; i < indices.size(); i++) {
495 builder.create_new_range_constraint(indices[i], 79);
496 }
497 builder.create_unconstrained_gates(indices);
498 bool result = CircuitChecker::check(builder);
499 EXPECT_EQ(result, false);
500 }
501 {
503 auto indices =
504 add_variables(builder, { 1, 0, 3, 80, 5, 6, 29, 8, 15, 11, 32, 21, 42, 79, 16, 10, 3, 26, 13, 14 });
505 for (size_t i = 0; i < indices.size(); i++) {
506 builder.create_new_range_constraint(indices[i], 79);
507 }
508 builder.create_unconstrained_gates(indices);
509 bool result = CircuitChecker::check(builder);
510 EXPECT_EQ(result, false);
511 }
512}
513
514TEST(UltraCircuitBuilder, RangeWithGates)
515{
517 auto idx = add_variables(builder, { 1, 2, 3, 4, 5, 6, 7, 8 });
518 for (size_t i = 0; i < idx.size(); i++) {
519 builder.create_new_range_constraint(idx[i], 8);
520 }
521
522 builder.create_add_gate({ idx[0], idx[1], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -3 });
523 builder.create_add_gate({ idx[2], idx[3], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -7 });
524 builder.create_add_gate({ idx[4], idx[5], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -11 });
525 builder.create_add_gate({ idx[6], idx[7], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -15 });
526 bool result = CircuitChecker::check(builder);
527 EXPECT_EQ(result, true);
528}
529
530TEST(UltraCircuitBuilder, RangeWithGatesWhereRangeIsNotAPowerOfTwo)
531{
533 auto idx = add_variables(builder, { 1, 2, 3, 4, 5, 6, 7, 8 });
534 for (size_t i = 0; i < idx.size(); i++) {
535 builder.create_new_range_constraint(idx[i], 12);
536 }
537
538 builder.create_add_gate({ idx[0], idx[1], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -3 });
539 builder.create_add_gate({ idx[2], idx[3], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -7 });
540 builder.create_add_gate({ idx[4], idx[5], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -11 });
541 builder.create_add_gate({ idx[6], idx[7], builder.zero_idx(), fr::one(), fr::one(), fr::zero(), -15 });
542 bool result = CircuitChecker::check(builder);
543 EXPECT_EQ(result, true);
544}
545
546TEST(UltraCircuitBuilder, SortWidgetComplex)
547{
548 {
549
551 std::vector<fr> a = { 1, 3, 4, 7, 7, 8, 11, 14, 15, 15, 18, 19, 21, 21, 24, 25, 26, 27, 30, 32 };
552 std::vector<uint32_t> ind;
553 for (size_t i = 0; i < a.size(); i++)
554 ind.emplace_back(builder.add_variable(a[i]));
555 builder.create_sort_constraint(ind);
556
557 bool result = CircuitChecker::check(builder);
558 EXPECT_EQ(result, true);
559 }
560 {
561
563 std::vector<fr> a = { 1, 3, 4, 7, 7, 8, 16, 14, 15, 15, 18, 19, 21, 21, 24, 25, 26, 27, 30, 32 };
564 std::vector<uint32_t> ind;
565 for (size_t i = 0; i < a.size(); i++)
566 ind.emplace_back(builder.add_variable(a[i]));
567 builder.create_sort_constraint(ind);
568
569 bool result = CircuitChecker::check(builder);
570 EXPECT_EQ(result, false);
571 }
572}
574{
576 fr a = fr::one();
577 fr b = fr(2);
578 fr c = fr(3);
579 fr d = fr(8);
580
581 auto a_idx = builder.add_variable(a);
582 auto b_idx = builder.add_variable(b);
583 auto c_idx = builder.add_variable(c);
584 auto d_idx = builder.add_variable(d);
585 builder.create_sort_constraint({ a_idx, b_idx, c_idx, d_idx });
586
587 bool result = CircuitChecker::check(builder);
588 EXPECT_EQ(result, false);
589}
590
591TEST(UltraCircuitBuilder, ComposedRangeConstraint)
592{
593 // even num bits - not divisible by 3
595 auto c = fr::random_element();
596 auto d = uint256_t(c).slice(0, 133);
597 auto e = fr(d);
598 auto a_idx = builder.add_variable(fr(e));
599 builder.create_add_gate({ a_idx, builder.zero_idx(), builder.zero_idx(), 1, 0, 0, -fr(e) });
600 builder.decompose_into_default_range(a_idx, 134);
601
602 // odd num bits - divisible by 3
603 auto c_1 = fr::random_element();
604 auto d_1 = uint256_t(c_1).slice(0, 126);
605 auto e_1 = fr(d_1);
606 auto a_idx_1 = builder.add_variable(fr(e_1));
607 builder.create_add_gate({ a_idx_1, builder.zero_idx(), builder.zero_idx(), 1, 0, 0, -fr(e_1) });
608 builder.decompose_into_default_range(a_idx_1, 127);
609
610 bool result = CircuitChecker::check(builder);
611 EXPECT_EQ(result, true);
612}
613
614static std::array<uint32_t, 2> helper_non_native_multiplication(UltraCircuitBuilder& builder,
615 const fq& a,
616 const fq& b,
617 const uint256_t& q,
618 const uint256_t& r,
619 const uint256_t& modulus)
620{
621 // Splits a 256-bit integer into 4 68-bit limbs
622 const auto split_into_limbs = [&](const uint512_t& input) {
623 constexpr size_t NUM_BITS = 68;
624 std::array<fr, 4> limbs;
625 limbs[0] = input.slice(0, NUM_BITS).lo;
626 limbs[1] = input.slice(NUM_BITS * 1, NUM_BITS * 2).lo;
627 limbs[2] = input.slice(NUM_BITS * 2, NUM_BITS * 3).lo;
628 limbs[3] = input.slice(NUM_BITS * 3, NUM_BITS * 4).lo;
629 return limbs;
630 };
631
632 // Adds the 4 limbs as circuit variables and returns their indices
633 const auto get_limb_witness_indices = [&](const std::array<fr, 4>& limbs) {
634 std::array<uint32_t, 4> limb_indices;
635 limb_indices[0] = builder.add_variable(limbs[0]);
636 limb_indices[1] = builder.add_variable(limbs[1]);
637 limb_indices[2] = builder.add_variable(limbs[2]);
638 limb_indices[3] = builder.add_variable(limbs[3]);
639 return limb_indices;
640 };
641
642 // Compute negative modulus: (-p) := 2^T - p
643 const uint512_t BINARY_BASIS_MODULUS = uint512_t(1) << (68 * 4);
644 auto modulus_limbs = split_into_limbs(BINARY_BASIS_MODULUS - uint512_t(modulus));
645
646 // Add a, b, q, r as circuit variables
647 const auto a_indices = get_limb_witness_indices(split_into_limbs(uint256_t(a)));
648 const auto b_indices = get_limb_witness_indices(split_into_limbs(uint256_t(b)));
649 const auto q_indices = get_limb_witness_indices(split_into_limbs(uint256_t(q)));
650 const auto r_indices = get_limb_witness_indices(split_into_limbs(uint256_t(r)));
651
652 // Prepare inputs for non-native multiplication gadget
654 a_indices, b_indices, q_indices, r_indices, modulus_limbs,
655 };
656 const auto [lo_1_idx, hi_1_idx] = builder.evaluate_non_native_field_multiplication(inputs);
657
658 return { lo_1_idx, hi_1_idx };
659}
660
661TEST(UltraCircuitBuilder, NonNativeFieldMultiplication)
662{
663 const size_t num_iterations = 50;
664 for (size_t i = 0; i < num_iterations; i++) {
666
669 uint256_t modulus = fq::modulus;
670
673 uint1024_t p_big = uint512_t(uint256_t(modulus));
674
675 uint1024_t q_big = (a_big * b_big) / p_big;
676 uint1024_t r_big = (a_big * b_big) % p_big;
677
678 uint256_t q(q_big.lo.lo);
679 uint256_t r(r_big.lo.lo);
680
681 const auto [lo_1_idx, hi_1_idx] = helper_non_native_multiplication(builder, a, b, q, r, modulus);
682
683 // Range check the carry (output) lo and hi limbs
684 const bool is_low_70_bits = uint256_t(builder.get_variable(lo_1_idx)).get_msb() < 70;
685 const bool is_high_70_bits = uint256_t(builder.get_variable(hi_1_idx)).get_msb() < 70;
686 if (is_low_70_bits && is_high_70_bits) {
687 // Uses more efficient NNF range check if both limbs are < 2^70
688 builder.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70);
689 } else {
690 // Fallback to default range checks
691 builder.decompose_into_default_range(lo_1_idx, 72);
692 builder.decompose_into_default_range(hi_1_idx, 72);
693 }
694
695 bool result = CircuitChecker::check(builder);
696 EXPECT_EQ(result, true);
697 }
698}
699
700TEST(UltraCircuitBuilder, NonNativeFieldMultiplicationRegression)
701{
703
704 // Edge case values
705 uint256_t a_u256 = uint256_t("0x00ab1504deacff852326adf4a01099e9340f232e2a631042852fce3c4eb8a51b");
706 uint256_t b_u256 = uint256_t("0x1be457323502cfcd85f8cfa54c8c4fea146b9db2a7d86b29d966d61b714ee249");
707 uint256_t q_u256 = uint256_t("0x00629b9d576dfc6b5c28a4a254d5e8e3384124f6a898858e95265254a01414d5");
708 uint256_t r_u256 = uint256_t("0x2c1590eb70a48dce72f7686bbf79b59bf7926c99bc16aba92e474c65a04ea2a0");
709 uint256_t modulus = fq::modulus;
710
711 // Check if native computation yeils same q and r
712 uint1024_t a_big = uint512_t(a_u256);
713 uint1024_t b_big = uint512_t(b_u256);
714 uint1024_t p_big = uint512_t(uint256_t(modulus));
715
716 uint1024_t q_big = (a_big * b_big) / p_big;
717 uint1024_t r_big = (a_big * b_big) % p_big;
718 uint256_t q_computed(q_big.lo.lo);
719 uint256_t r_computed(r_big.lo.lo);
720
721 EXPECT_EQ(q_computed, q_u256);
722 EXPECT_EQ(r_computed, r_u256);
723
724 // This edge case leads to the carry limb being > 2^70, so it used to fail when appying a 2^70 range check
725 // (with range_constrain_two_limbs). Now it should work since we fallback to default range checks in such a case.
726 const auto [lo_1_idx, hi_1_idx] =
727 helper_non_native_multiplication(builder, a_u256, b_u256, q_u256, r_u256, modulus);
728
729 // Range check the carry (output) lo and hi limbs
730 const bool is_high_70_bits = uint256_t(builder.get_variable(hi_1_idx)).get_msb() < 70;
731 ASSERT(is_high_70_bits == false); // Regression should hit this case
732
733 // Decompose into default range: these should work even if the limbs are > 2^70
734 builder.decompose_into_default_range(lo_1_idx, 72);
735 builder.decompose_into_default_range(hi_1_idx, 72);
736 bool result_a = CircuitChecker::check(builder);
737 EXPECT_EQ(result_a, true);
738
739 // Using NNF range check should fail here
740 builder.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70);
741 bool result_b = CircuitChecker::check(builder);
742 EXPECT_EQ(result_b, false);
743 EXPECT_EQ(builder.err(), "range_constrain_two_limbs: hi limb.");
744}
745
750TEST(UltraCircuitBuilder, NonNativeFieldMultiplicationSortCheck)
751{
753
756 uint256_t modulus = fq::modulus;
757
760 uint1024_t p_big = uint512_t(uint256_t(modulus));
761
762 uint1024_t q_big = (a_big * b_big) / p_big;
763 uint1024_t r_big = (a_big * b_big) % p_big;
764
765 uint256_t q(q_big.lo.lo);
766 uint256_t r(r_big.lo.lo);
767
768 const auto [lo_1_idx, hi_1_idx] = helper_non_native_multiplication(builder, a, b, q, r, modulus);
769
770 // Range check the carry (output) lo and hi limbs
771 const bool is_low_70_bits = uint256_t(builder.get_variable(lo_1_idx)).get_msb() < 70;
772 const bool is_high_70_bits = uint256_t(builder.get_variable(hi_1_idx)).get_msb() < 70;
773 if (is_low_70_bits && is_high_70_bits) {
774 // Uses more efficient NNF range check if both limbs are < 2^70
775 builder.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70);
776 } else {
777 // Fallback to default range checks
778 builder.decompose_into_default_range(lo_1_idx, 72);
779 builder.decompose_into_default_range(hi_1_idx, 72);
780 }
781
782 bool result = CircuitChecker::check(builder);
783 EXPECT_EQ(result, true);
784
785 // Everything above was copied from the previous test.
786 // Check that in the nnf blocks, the other selectors besides the nnf selector are zero
787 for (size_t i = 0; i < builder.blocks.nnf.size(); ++i) {
788 EXPECT_EQ(builder.blocks.nnf.q_arith()[i], 0);
789 EXPECT_EQ(builder.blocks.nnf.q_delta_range()[i], 0);
790 EXPECT_EQ(builder.blocks.nnf.q_elliptic()[i], 0);
791 EXPECT_EQ(builder.blocks.nnf.q_lookup_type()[i], 0);
792 EXPECT_EQ(builder.blocks.nnf.q_poseidon2_external()[i], 0);
793 EXPECT_EQ(builder.blocks.nnf.q_poseidon2_internal()[i], 0);
794 }
795}
796
798{
800
801 uint32_t rom_values[8]{
802 builder.add_variable(fr::random_element()), builder.add_variable(fr::random_element()),
803 builder.add_variable(fr::random_element()), builder.add_variable(fr::random_element()),
804 builder.add_variable(fr::random_element()), builder.add_variable(fr::random_element()),
805 builder.add_variable(fr::random_element()), builder.add_variable(fr::random_element()),
806 };
807
808 size_t rom_id = builder.create_ROM_array(8);
809
810 for (size_t i = 0; i < 8; ++i) {
811 builder.set_ROM_element(rom_id, i, rom_values[i]);
812 }
813
814 uint32_t a_idx = builder.read_ROM_array(rom_id, builder.add_variable(5));
815 EXPECT_EQ(a_idx != rom_values[5], true);
816 uint32_t b_idx = builder.read_ROM_array(rom_id, builder.add_variable(4));
817 uint32_t c_idx = builder.read_ROM_array(rom_id, builder.add_variable(1));
818
819 const auto d_value = builder.get_variable(a_idx) + builder.get_variable(b_idx) + builder.get_variable(c_idx);
820 uint32_t d_idx = builder.add_variable(d_value);
821
822 builder.create_big_add_gate({
823 a_idx,
824 b_idx,
825 c_idx,
826 d_idx,
827 1,
828 1,
829 1,
830 -1,
831 0,
832 });
833
834 bool result = CircuitChecker::check(builder);
835 EXPECT_EQ(result, true);
836}
837
843{
845
846 // Initialize a length 1 RAM array with a single value
847 fr ram_value = 5;
848 uint32_t ram_value_idx = builder.add_variable(ram_value);
849 size_t ram_id = builder.create_RAM_array(/*array_size=*/1);
850 builder.init_RAM_element(ram_id, /*index_value=*/0, ram_value_idx);
851
852 // Read from the RAM array we just created (at the 0th index)
853 uint32_t read_idx = builder.add_variable(0);
854 uint32_t a_idx = builder.read_RAM_array(ram_id, read_idx);
855
856 // Use the result in a simple arithmetic gate
857 builder.create_big_add_gate({
858 a_idx,
859 builder.zero_idx(),
860 builder.zero_idx(),
861 builder.zero_idx(),
862 -1,
863 0,
864 0,
865 0,
866 builder.get_variable(ram_value_idx),
867 });
868
869 EXPECT_TRUE(CircuitChecker::check(builder));
870}
871
873{
875
876 uint32_t ram_values[8]{
877 builder.add_variable(fr::random_element()), builder.add_variable(fr::random_element()),
878 builder.add_variable(fr::random_element()), builder.add_variable(fr::random_element()),
879 builder.add_variable(fr::random_element()), builder.add_variable(fr::random_element()),
880 builder.add_variable(fr::random_element()), builder.add_variable(fr::random_element()),
881 };
882
883 size_t ram_id = builder.create_RAM_array(8);
884
885 for (size_t i = 0; i < 8; ++i) {
886 builder.init_RAM_element(ram_id, i, ram_values[i]);
887 }
888
889 uint32_t a_idx = builder.read_RAM_array(ram_id, builder.add_variable(5));
890 EXPECT_EQ(a_idx != ram_values[5], true);
891
892 uint32_t b_idx = builder.read_RAM_array(ram_id, builder.add_variable(4));
893 uint32_t c_idx = builder.read_RAM_array(ram_id, builder.add_variable(1));
894
895 builder.write_RAM_array(ram_id, builder.add_variable(4), builder.add_variable(500));
896 uint32_t d_idx = builder.read_RAM_array(ram_id, builder.add_variable(4));
897
898 EXPECT_EQ(builder.get_variable(d_idx), 500);
899
900 // ensure these vars get used in another arithmetic gate
901 const auto e_value = builder.get_variable(a_idx) + builder.get_variable(b_idx) + builder.get_variable(c_idx) +
902 builder.get_variable(d_idx);
903 uint32_t e_idx = builder.add_variable(e_value);
904
905 builder.create_big_add_gate(
906 {
907 a_idx,
908 b_idx,
909 c_idx,
910 d_idx,
911 -1,
912 -1,
913 -1,
914 -1,
915 0,
916 },
917 true);
918 builder.create_big_add_gate(
919 {
920 builder.zero_idx(),
921 builder.zero_idx(),
922 builder.zero_idx(),
923 e_idx,
924 0,
925 0,
926 0,
927 0,
928 0,
929 },
930 false);
931
932 bool result = CircuitChecker::check(builder);
933 EXPECT_EQ(result, true);
934
935 // Test the builder copy constructor for a circuit with RAM gates
936 UltraCircuitBuilder duplicate_builder{ builder };
937
938 EXPECT_EQ(duplicate_builder.get_estimated_num_finalized_gates(), builder.get_estimated_num_finalized_gates());
939 EXPECT_TRUE(CircuitChecker::check(duplicate_builder));
940}
941
942TEST(UltraCircuitBuilder, RangeChecksOnDuplicates)
943{
945
946 uint32_t a = builder.add_variable(100);
947 uint32_t b = builder.add_variable(100);
948 uint32_t c = builder.add_variable(100);
949 uint32_t d = builder.add_variable(100);
950
951 builder.assert_equal(a, b);
952 builder.assert_equal(a, c);
953 builder.assert_equal(a, d);
954
955 builder.create_new_range_constraint(a, 1000);
956 builder.create_new_range_constraint(b, 1001);
957 builder.create_new_range_constraint(c, 999);
958 builder.create_new_range_constraint(d, 1000);
959
960 builder.create_big_add_gate(
961 {
962 a,
963 b,
964 c,
965 d,
966 0,
967 0,
968 0,
969 0,
970 0,
971 },
972 false);
973 bool result = CircuitChecker::check(builder);
974 EXPECT_EQ(result, true);
975}
976
977TEST(UltraCircuitBuilder, CheckCircuitShowcase)
978{
980 // check_circuit allows us to check correctness on the go
981
982 uint32_t a = builder.add_variable(0xdead);
983 uint32_t b = builder.add_variable(0xbeef);
984 // Let's create 2 gates that will bind these 2 variables to be one these two values
985 builder.create_poly_gate(
986 { a, a, builder.zero_idx(), fr(1), -fr(0xdead) - fr(0xbeef), 0, 0, fr(0xdead) * fr(0xbeef) });
987 builder.create_poly_gate(
988 { b, b, builder.zero_idx(), fr(1), -fr(0xdead) - fr(0xbeef), 0, 0, fr(0xdead) * fr(0xbeef) });
989
990 // We can check if this works
991 EXPECT_EQ(CircuitChecker::check(builder), true);
992
993 // Now let's create a range constraint for b
994 builder.create_new_range_constraint(b, 0xbeef);
995
996 // We can check if this works
997 EXPECT_EQ(CircuitChecker::check(builder), true);
998
999 // But what if we now assert b to be equal to a?
1000 builder.assert_equal(a, b, "Oh no");
1001
1002 // It fails, because a is 0xdead and it can't fit in the range constraint
1003 EXPECT_EQ(CircuitChecker::check(builder), false);
1004
1005 // But if we force them both back to be 0xbeef...
1006 uint32_t c = builder.add_variable(0xbeef);
1007 builder.assert_equal(c, b);
1008
1009 // The circuit will magically pass again
1010 EXPECT_EQ(CircuitChecker::check(builder), true);
1011}
1012
1013} // namespace bb
#define ASSERT(expression,...)
Definition assert.hpp:77
virtual uint32_t add_variable(const FF &in)
FF get_variable(const uint32_t index) const
Get the value of the variable v_{index}.
static void add_lookup_gates(Builder &builder, size_t num_iterations=1)
Add lookup gates using the uint32 XOR lookup table (table size 4096)
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
plookup::ReadData< uint32_t > create_gates_from_plookup_accumulators(const plookup::MultiTableId &id, const plookup::ReadData< FF > &read_values, const uint32_t key_a_index, std::optional< uint32_t > key_b_index=std::nullopt)
Perform a series of lookups, one for each 'row' in read_values.
static AffineElement commit_native(const std::vector< Fq > &inputs, GeneratorContext context={})
Given a vector of fields, generate a pedersen commitment using the indexed generators.
Definition pedersen.cpp:24
element class. Implements ecc group arithmetic using Jacobian coordinates See https://hyperelliptic....
Definition element.hpp:33
constexpr element dbl() const noexcept
static std::vector< affine_element > derive_generators(const std::vector< uint8_t > &domain_separator_bytes, const size_t num_generators, const size_t starting_index=0)
Derives generator points via hash-to-curve.
Definition group.hpp:87
constexpr uint256_t slice(uint64_t start, uint64_t end) const
constexpr uint64_t get_msb() const
static constexpr affine_element lhs_generator_point()
AluTraceBuilder builder
Definition alu.test.cpp:123
FF a
FF b
grumpkin::g1::affine_element affine_element
Definition c_bind.hpp:15
uintx< uint256_t > uint512_t
Definition uintx.hpp:307
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:190
ReadData< bb::fr > get_lookup_accumulators(const MultiTableId id, const fr &key_a, const fr &key_b, const bool is_2_to_1_lookup)
Given a table ID and the key(s) for a key-value lookup, return the lookup accumulators.
@ FIXED_BASE_LEFT_LO
Definition types.hpp:100
Entry point for Barretenberg command-line interface.
std::vector< uint32_t > add_variables(UltraCircuitBuilder &builder, std::vector< fr > variables)
void write(std::vector< uint8_t > &buf, ClientIVC::VerificationKey const &vk)
field< Bn254FrParams > fr
Definition fr.hpp:174
C slice(C const &container, size_t start)
Definition container.hpp:9
UltraCircuitBuilder_< UltraExecutionTraceBlocks > UltraCircuitBuilder
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static constexpr field neg_one()
static constexpr field one()
static constexpr uint256_t modulus
static field random_element(numeric::RNG *engine=nullptr) noexcept
static constexpr field zero()
static constexpr size_t MAX_TABLE_SIZE
static constexpr size_t BITS_PER_TABLE
static constexpr size_t BITS_PER_LO_SCALAR
static constexpr size_t NUM_TABLES_PER_LO_MULTITABLE