Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ultra_circuit_builder_nonnative.test.cpp
Go to the documentation of this file.
4
5#include <gtest/gtest.h>
6
7using namespace bb;
8
20class UltraCircuitBuilderNonNative : public ::testing::Test {
21 protected:
23 static inline const uint512_t BINARY_BASIS_MODULUS = uint512_t(1) << (LIMB_BITS * 4);
24
25 // Number of NNF gates produced by a single partial multiplication
26 static constexpr size_t NNF_GATES_PER_PARTIAL_MUL = 4;
27 // Number of padding gates added to NNF block by finalize_circuit()
28 static constexpr size_t NNF_ENSURE_NONZERO_PADDING = 0;
29
30 // Generate 4 random field elements (one per limb)
35
36 // Splits a 256-bit integer into 4 68-bit limbs
38 {
40 limbs[0] = input.slice(0, LIMB_BITS).lo;
41 limbs[1] = input.slice(LIMB_BITS * 1, LIMB_BITS * 2).lo;
42 limbs[2] = input.slice(LIMB_BITS * 2, LIMB_BITS * 3).lo;
43 limbs[3] = input.slice(LIMB_BITS * 3, LIMB_BITS * 4).lo;
44 return limbs;
45 }
46
47 // Adds 4 limbs as circuit variables and returns their indices
48 static std::array<uint32_t, 4> get_limb_witness_indices(UltraCircuitBuilder& builder,
49 const std::array<fr, 4>& limbs)
50 {
51 std::array<uint32_t, 4> limb_indices;
52 limb_indices[0] = builder.add_variable(limbs[0]);
53 limb_indices[1] = builder.add_variable(limbs[1]);
54 limb_indices[2] = builder.add_variable(limbs[2]);
55 limb_indices[3] = builder.add_variable(limbs[3]);
56 return limb_indices;
57 }
58
59 // Helper to set up and evaluate a non-native field multiplication
61 const uint256_t& a,
62 const uint256_t& b,
63 const uint256_t& q,
64 const uint256_t& r,
65 const uint256_t& modulus)
66 {
67 // Compute negative modulus: (-p) := 2^T - p
68 auto modulus_limbs = split_into_limbs(BINARY_BASIS_MODULUS - uint512_t(modulus));
69
70 // Add a, b, q, r as circuit variables
75
76 // Prepare inputs for non-native multiplication gadget
78 a_indices, b_indices, q_indices, r_indices, modulus_limbs,
79 };
80 const auto [lo_1_idx, hi_1_idx] = builder.evaluate_non_native_field_multiplication(inputs);
81
82 return { lo_1_idx, hi_1_idx };
83 }
84
85 // Compute quotient and remainder for a * b mod p
87 const uint256_t& b,
88 const uint256_t& modulus)
89 {
90 uint1024_t a_big = uint512_t(a);
91 uint1024_t b_big = uint512_t(b);
92 uint1024_t p_big = uint512_t(modulus);
93
94 uint1024_t q_big = (a_big * b_big) / p_big;
95 uint1024_t r_big = (a_big * b_big) % p_big;
96
97 return { uint256_t(q_big.lo.lo), uint256_t(r_big.lo.lo) };
98 }
99
100 // Type aliases matching builder API for addition/subtraction
103
104 // Data structure for addition/subtraction test inputs
115
116 // Create random limb values for testing
118 {
119 return AddSubData{
121 .y_limbs = random_limbs(),
122 .x_prime = fr::random_element(),
123 .y_prime = fr::random_element(),
124 .x_scales = { fr(1), fr(1), fr(1), fr(1) },
125 .y_scales = { fr(1), fr(1), fr(1), fr(1) },
126 .addconsts = { fr(0), fr(0), fr(0), fr(0) },
127 .addconstp = fr(0),
128 };
129 }
130
131 // Create add_simple tuples from test data and witness indices
134 {
135 // Add witness variables
136 std::array<uint32_t, 4> x_idx, y_idx;
137 for (size_t i = 0; i < 4; i++) {
138 x_idx[i] = builder.add_variable(data.x_limbs[i]);
139 y_idx[i] = builder.add_variable(data.y_limbs[i]);
140 }
141 uint32_t x_p_idx = builder.add_variable(data.x_prime);
142 uint32_t y_p_idx = builder.add_variable(data.y_prime);
143
144 // Build add_simple tuples: ((x_idx, x_scale), (y_idx, y_scale), addconst)
145 add_simple limb0 = { { x_idx[0], data.x_scales[0] }, { y_idx[0], data.y_scales[0] }, data.addconsts[0] };
146 add_simple limb1 = { { x_idx[1], data.x_scales[1] }, { y_idx[1], data.y_scales[1] }, data.addconsts[1] };
147 add_simple limb2 = { { x_idx[2], data.x_scales[2] }, { y_idx[2], data.y_scales[2] }, data.addconsts[2] };
148 add_simple limb3 = { { x_idx[3], data.x_scales[3] }, { y_idx[3], data.y_scales[3] }, data.addconsts[3] };
149 auto limbp = std::make_tuple(x_p_idx, y_p_idx, data.addconstp);
150
151 return { limb0, limb1, limb2, limb3, limbp };
152 }
153
154 // Helper to create partial multiplication inputs and queue the operation
156 const std::array<fr, 4>& a_limbs,
157 const std::array<fr, 4>& b_limbs)
158 {
159 const auto a_indices = get_limb_witness_indices(builder, a_limbs);
160 const auto b_indices = get_limb_witness_indices(builder, b_limbs);
161
162 non_native_partial_multiplication_witnesses<fr> input{ .a = a_indices, .b = b_indices };
163 return builder.queue_partial_non_native_field_multiplication(input);
164 }
165
166 // Compute expected lo_0 and hi_1 values for partial multiplication.
167 //
168 // Given two non-native field elements represented as 4 limbs each:
169 // a = a[0] + a[1]*L + a[2]*L² + a[3]*L³ (where L = 2^LIMB_BITS)
170 // b = b[0] + b[1]*L + b[2]*L² + b[3]*L³
171 //
172 // The product a*b expands via schoolbook multiplication to terms at powers of L:
173 // L⁰: a[0]*b[0]
174 // L¹: a[1]*b[0] + a[0]*b[1]
175 // L²: a[2]*b[0] + a[1]*b[1] + a[0]*b[2]
176 // L³: a[3]*b[0] + a[2]*b[1] + a[1]*b[2] + a[0]*b[3]
177 // (higher powers of L can be ignored)
178 //
179 // The partial products group these terms:
180 // lo_0 = L⁰ and L¹ terms (low 2*LIMB_BITS portion)
181 // hi_1 = L² and L³ terms (high portion, split into hi_0 + remaining for constraint efficiency)
183 {
184 const fr LIMB_SHIFT = fr(uint256_t(1) << LIMB_BITS);
185
186 fr lo_0 = a[0] * b[0] + ((a[1] * b[0] + a[0] * b[1]) * LIMB_SHIFT);
187 fr hi_0 = a[2] * b[0] + a[0] * b[2] + ((a[0] * b[3] + a[3] * b[0]) * LIMB_SHIFT);
188 fr hi_1 = hi_0 + a[1] * b[1] + ((a[1] * b[2] + a[2] * b[1]) * LIMB_SHIFT);
189
190 return { lo_0, hi_1 };
191 }
192};
193
194// Verifies that valid non-native field multiplications pass the circuit checker
196{
197 const size_t num_iterations = 50;
198 for (size_t i = 0; i < num_iterations; i++) {
200
203 uint256_t modulus = fq::modulus;
204
205 auto [q, r] = compute_quotient_remainder(a, b, modulus);
206 const auto [lo_1_idx, hi_1_idx] = create_non_native_multiplication(builder, a, b, q, r, modulus);
207
208 // Range check the carry (output) lo and hi limbs
209 const bool is_low_70_bits = uint256_t(builder.get_variable(lo_1_idx)).get_msb() < 70;
210 const bool is_high_70_bits = uint256_t(builder.get_variable(hi_1_idx)).get_msb() < 70;
211 if (is_low_70_bits && is_high_70_bits) {
212 // Uses more efficient NNF range check if both limbs are < 2^70
213 builder.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70);
214 } else {
215 // Fallback to default range checks
216 builder.create_limbed_range_constraint(lo_1_idx, 72);
217 builder.create_limbed_range_constraint(hi_1_idx, 72);
218 }
219
220 EXPECT_TRUE(CircuitChecker::check(builder));
221 }
222}
223
224// Regression test: carry limb > 2^70 requires fallback to default range checks
225TEST_F(UltraCircuitBuilderNonNative, MultiplicationLargeCarryRegression)
226{
228
229 // Edge case values that produce carry > 2^70
230 uint256_t a = uint256_t("0x00ab1504deacff852326adf4a01099e9340f232e2a631042852fce3c4eb8a51b");
231 uint256_t b = uint256_t("0x1be457323502cfcd85f8cfa54c8c4fea146b9db2a7d86b29d966d61b714ee249");
232 uint256_t q_expected = uint256_t("0x00629b9d576dfc6b5c28a4a254d5e8e3384124f6a898858e95265254a01414d5");
233 uint256_t r_expected = uint256_t("0x2c1590eb70a48dce72f7686bbf79b59bf7926c99bc16aba92e474c65a04ea2a0");
234 uint256_t modulus = fq::modulus;
235
236 // Verify that our expected q, r are correct
237 auto [q_computed, r_computed] = compute_quotient_remainder(a, b, modulus);
238 EXPECT_EQ(q_computed, q_expected);
239 EXPECT_EQ(r_computed, r_expected);
240
241 // This edge case leads to the carry limb being > 2^70, so it used to fail when applying a 2^70 range check
242 // (with range_constrain_two_limbs). Now it should work since we fallback to default range checks in such a case.
243 const auto [lo_1_idx, hi_1_idx] = create_non_native_multiplication(builder, a, b, q_expected, r_expected, modulus);
244
245 // Range check the carry (output) lo and hi limbs
246 const bool is_high_70_bits = uint256_t(builder.get_variable(hi_1_idx)).get_msb() < 70;
247 BB_ASSERT(is_high_70_bits == false); // Regression should hit this case
248
249 // Decompose into default range: these should work even if the limbs are > 2^70
250 builder.create_limbed_range_constraint(lo_1_idx, 72);
251 builder.create_limbed_range_constraint(hi_1_idx, 72);
252 EXPECT_TRUE(CircuitChecker::check(builder));
253
254 // Using NNF range check should fail here
255 builder.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70);
256 EXPECT_FALSE(CircuitChecker::check(builder));
257 EXPECT_EQ(builder.err(), "range_constrain_two_limbs: hi limb.");
258}
259
260// Verifies non-native field addition with various scaling factors
262{
263 // Test with identity scaling (z = x + y)
264 {
266 auto data = create_random_add_sub_data();
267 auto [limb0, limb1, limb2, limb3, limbp] = create_add_sub_inputs(builder, data);
268 builder.evaluate_non_native_field_addition(limb0, limb1, limb2, limb3, limbp);
269 EXPECT_TRUE(CircuitChecker::check(builder));
270 }
271
272 // Test with different scaling factors per limb and non-zero addconstp
273 {
275 auto data = create_random_add_sub_data();
276 data.x_scales = { fr(2), fr(3), fr(5), fr(7) };
277 data.y_scales = { fr(11), fr(13), fr(17), fr(19) };
278 data.addconsts = { fr(100), fr(200), fr(300), fr(400) };
279 data.addconstp = fr(500);
280
281 auto [limb0, limb1, limb2, limb3, limbp] = create_add_sub_inputs(builder, data);
282 builder.evaluate_non_native_field_addition(limb0, limb1, limb2, limb3, limbp);
283 EXPECT_TRUE(CircuitChecker::check(builder));
284 }
285
286 // Test with negative and zero scaling factors
287 {
289 auto data = create_random_add_sub_data();
290 data.x_scales = { fr(-1), fr(0), fr(1), fr(-2) };
291 data.y_scales = { fr(1), fr(-1), fr(0), fr(2) };
292 data.addconsts = { fr(0), fr(-50), fr(50), fr(0) };
293 data.addconstp = fr(-100);
294
295 auto [limb0, limb1, limb2, limb3, limbp] = create_add_sub_inputs(builder, data);
296 builder.evaluate_non_native_field_addition(limb0, limb1, limb2, limb3, limbp);
297 EXPECT_TRUE(CircuitChecker::check(builder));
298 }
299
300 // Edge case: all zeros
301 {
303 AddSubData data{
304 .x_limbs = { fr(0), fr(0), fr(0), fr(0) },
305 .y_limbs = { fr(0), fr(0), fr(0), fr(0) },
306 .x_prime = fr(0),
307 .y_prime = fr(0),
308 .x_scales = { fr(1), fr(1), fr(1), fr(1) },
309 .y_scales = { fr(1), fr(1), fr(1), fr(1) },
310 .addconsts = { fr(0), fr(0), fr(0), fr(0) },
311 .addconstp = fr(0),
312 };
313 auto [limb0, limb1, limb2, limb3, limbp] = create_add_sub_inputs(builder, data);
314 builder.evaluate_non_native_field_addition(limb0, limb1, limb2, limb3, limbp);
315 EXPECT_TRUE(CircuitChecker::check(builder));
316 }
317
318 // Edge case: x == y (doubling)
319 {
321 fr val0 = fr::random_element();
322 fr val1 = fr::random_element();
323 fr val2 = fr::random_element();
324 fr val3 = fr::random_element();
325 fr valp = fr::random_element();
326 AddSubData data{
327 .x_limbs = { val0, val1, val2, val3 },
328 .y_limbs = { val0, val1, val2, val3 },
329 .x_prime = valp,
330 .y_prime = valp,
331 .x_scales = { fr(1), fr(1), fr(1), fr(1) },
332 .y_scales = { fr(1), fr(1), fr(1), fr(1) },
333 .addconsts = { fr(0), fr(0), fr(0), fr(0) },
334 .addconstp = fr(0),
335 };
336 auto [limb0, limb1, limb2, limb3, limbp] = create_add_sub_inputs(builder, data);
337 builder.evaluate_non_native_field_addition(limb0, limb1, limb2, limb3, limbp);
338 EXPECT_TRUE(CircuitChecker::check(builder));
339 }
340}
341
342// Verifies non-native field subtraction with various scaling factors
344{
345 // Test with identity scaling (z = x - y)
346 {
348 auto data = create_random_add_sub_data();
349 auto [limb0, limb1, limb2, limb3, limbp] = create_add_sub_inputs(builder, data);
350 builder.evaluate_non_native_field_subtraction(limb0, limb1, limb2, limb3, limbp);
351 EXPECT_TRUE(CircuitChecker::check(builder));
352 }
353
354 // Test with different scaling factors per limb and non-zero addconstp
355 {
357 auto data = create_random_add_sub_data();
358 data.x_scales = { fr(2), fr(3), fr(5), fr(7) };
359 data.y_scales = { fr(11), fr(13), fr(17), fr(19) };
360 data.addconsts = { fr(100), fr(200), fr(300), fr(400) };
361 data.addconstp = fr(500);
362
363 auto [limb0, limb1, limb2, limb3, limbp] = create_add_sub_inputs(builder, data);
364 builder.evaluate_non_native_field_subtraction(limb0, limb1, limb2, limb3, limbp);
365 EXPECT_TRUE(CircuitChecker::check(builder));
366 }
367
368 // Test with negative and zero scaling factors
369 {
371 auto data = create_random_add_sub_data();
372 data.x_scales = { fr(-1), fr(0), fr(1), fr(-2) };
373 data.y_scales = { fr(1), fr(-1), fr(0), fr(2) };
374 data.addconsts = { fr(0), fr(-50), fr(50), fr(0) };
375 data.addconstp = fr(-100);
376
377 auto [limb0, limb1, limb2, limb3, limbp] = create_add_sub_inputs(builder, data);
378 builder.evaluate_non_native_field_subtraction(limb0, limb1, limb2, limb3, limbp);
379 EXPECT_TRUE(CircuitChecker::check(builder));
380 }
381
382 // Edge case: all zeros
383 {
385 AddSubData data{
386 .x_limbs = { fr(0), fr(0), fr(0), fr(0) },
387 .y_limbs = { fr(0), fr(0), fr(0), fr(0) },
388 .x_prime = fr(0),
389 .y_prime = fr(0),
390 .x_scales = { fr(1), fr(1), fr(1), fr(1) },
391 .y_scales = { fr(1), fr(1), fr(1), fr(1) },
392 .addconsts = { fr(0), fr(0), fr(0), fr(0) },
393 .addconstp = fr(0),
394 };
395 auto [limb0, limb1, limb2, limb3, limbp] = create_add_sub_inputs(builder, data);
396 builder.evaluate_non_native_field_subtraction(limb0, limb1, limb2, limb3, limbp);
397 EXPECT_TRUE(CircuitChecker::check(builder));
398 }
399
400 // Edge case: x == y (result is zero)
401 {
403 fr val0 = fr::random_element();
404 fr val1 = fr::random_element();
405 fr val2 = fr::random_element();
406 fr val3 = fr::random_element();
407 fr valp = fr::random_element();
408 AddSubData data{
409 .x_limbs = { val0, val1, val2, val3 },
410 .y_limbs = { val0, val1, val2, val3 },
411 .x_prime = valp,
412 .y_prime = valp,
413 .x_scales = { fr(1), fr(1), fr(1), fr(1) },
414 .y_scales = { fr(1), fr(1), fr(1), fr(1) },
415 .addconsts = { fr(0), fr(0), fr(0), fr(0) },
416 .addconstp = fr(0),
417 };
418 auto [limb0, limb1, limb2, limb3, limbp] = create_add_sub_inputs(builder, data);
419 builder.evaluate_non_native_field_subtraction(limb0, limb1, limb2, limb3, limbp);
420 EXPECT_TRUE(CircuitChecker::check(builder));
421 }
422}
423
424// Verifies that providing incorrect witnesses to multiplication causes failure
425TEST_F(UltraCircuitBuilderNonNative, MultiplicationInvalidWitnessFailure)
426{
427 // Helper to test that providing incorrect quotient/remainder causes failure
428 auto test_incorrect_qr = [](bool tamper_q, size_t limb_idx) {
430
433 uint256_t modulus = fq::modulus;
434 auto [q, r] = compute_quotient_remainder(a, b, modulus);
435
436 // Tamper with quotient or remainder
437 if (tamper_q) {
438 // Add 1 to a specific limb of q
439 auto q_limbs = split_into_limbs(uint256_t(q));
440 q_limbs[limb_idx] += fr(1);
441 q = uint256_t(q_limbs[0]) + (uint256_t(q_limbs[1]) << LIMB_BITS) +
442 (uint256_t(q_limbs[2]) << (LIMB_BITS * 2)) + (uint256_t(q_limbs[3]) << (LIMB_BITS * 3));
443 } else {
444 // Add 1 to a specific limb of r
445 auto r_limbs = split_into_limbs(uint256_t(r));
446 r_limbs[limb_idx] += fr(1);
447 r = uint256_t(r_limbs[0]) + (uint256_t(r_limbs[1]) << LIMB_BITS) +
448 (uint256_t(r_limbs[2]) << (LIMB_BITS * 2)) + (uint256_t(r_limbs[3]) << (LIMB_BITS * 3));
449 }
450
451 // Now a*b != q*p + r (the multiplication identity is violated)
452 const auto [lo_idx, hi_idx] = create_non_native_multiplication(builder, a, b, q, r, modulus);
453
454 // Add range constraints
455 builder.create_limbed_range_constraint(lo_idx, 72);
456 builder.create_limbed_range_constraint(hi_idx, 72);
457
458 EXPECT_FALSE(CircuitChecker::check(builder));
459 };
460
461 // Test tampering with each limb of q and r
462 for (size_t limb = 0; limb < 4; limb++) {
463 test_incorrect_qr(true, limb); // tamper q
464 test_incorrect_qr(false, limb); // tamper r
465 }
466}
467
468// Verifies that queue_partial_non_native_field_multiplication computes correct intermediate values
469TEST_F(UltraCircuitBuilderNonNative, PartialMultiplicationBasic)
470{
471 const size_t num_iterations = 10;
472 for (size_t i = 0; i < num_iterations; i++) {
474
475 std::array<fr, 4> a_limbs = random_limbs();
476 std::array<fr, 4> b_limbs = random_limbs();
477
478 const auto [lo_0_idx, hi_1_idx] = create_and_queue_partial_multiplication(builder, a_limbs, b_limbs);
479
480 // Verify returned witnesses contain expected values
481 auto [expected_lo_0, expected_hi_1] = compute_expected_partial_products(a_limbs, b_limbs);
482 EXPECT_EQ(builder.get_variable(lo_0_idx), expected_lo_0);
483 EXPECT_EQ(builder.get_variable(hi_1_idx), expected_hi_1);
484
485 // Verify circuit passes after finalization (which calls process_non_native_field_multiplications)
486 EXPECT_TRUE(CircuitChecker::check(builder));
487 }
488}
489
490// Verifies that duplicate partial multiplications are deduplicated
491TEST_F(UltraCircuitBuilderNonNative, PartialMultiplicationDeduplication)
492{
494 std::array<fr, 4> a_limbs = random_limbs();
495 std::array<fr, 4> b_limbs = random_limbs();
496
497 // Add witnesses once (to be reused)
498 const auto a_indices = get_limb_witness_indices(builder, a_limbs);
499 const auto b_indices = get_limb_witness_indices(builder, b_limbs);
500
501 // Queue the same multiplication multiple times using the same witness indices
502 const size_t num_duplicates = 5;
504 for (size_t i = 0; i < num_duplicates; i++) {
505 non_native_partial_multiplication_witnesses<fr> input{ .a = a_indices, .b = b_indices };
506 results.push_back(builder.queue_partial_non_native_field_multiplication(input));
507 }
508
509 // Cache should have all entries before finalization
510 EXPECT_EQ(builder.cached_partial_non_native_field_multiplications.size(), num_duplicates);
511
512 // Verify circuit passes (finalization happens internally via copy)
513 EXPECT_TRUE(CircuitChecker::check(builder));
514
515 // Finalize to check deduplication results
516 builder.finalize_circuit();
517
518 // After finalization, cache should be deduplicated to 1 entry
519 EXPECT_EQ(builder.cached_partial_non_native_field_multiplications.size(), 1U);
520
521 // NNF block size should be the same as single multiplication (deduplication worked)
522 EXPECT_EQ(builder.blocks.nnf.size(), NNF_GATES_PER_PARTIAL_MUL + NNF_ENSURE_NONZERO_PADDING);
523
524 // All results should have the same expected values
525 auto [expected_lo_0, expected_hi_1] = compute_expected_partial_products(a_limbs, b_limbs);
526 for (const auto& result : results) {
527 EXPECT_EQ(builder.get_variable(result[0]), expected_lo_0);
528 EXPECT_EQ(builder.get_variable(result[1]), expected_hi_1);
529 }
530}
531
532// Verifies deduplication correctly handles duplicates that result from an assert_equal
533TEST_F(UltraCircuitBuilderNonNative, PartialMultiplicationDedupeAssertEqual)
534{
536 std::array<fr, 4> a_limbs = random_limbs();
537 std::array<fr, 4> b_limbs = random_limbs();
538
539 // Create two sets of witness indices (different indices, same underlying values)
540 const auto a1_indices = get_limb_witness_indices(builder, a_limbs);
541 const auto b1_indices = get_limb_witness_indices(builder, b_limbs);
542 const auto a2_indices = get_limb_witness_indices(builder, a_limbs);
543 const auto b2_indices = get_limb_witness_indices(builder, b_limbs);
544
545 // Without assert_equal, these would be distinct indices and wouldn't deduplicate.
546 // assert_equal should make them behave as duplicates.
547 for (size_t i = 0; i < 4; i++) {
548 builder.assert_equal(a1_indices[i], a2_indices[i]);
549 builder.assert_equal(b1_indices[i], b2_indices[i]);
550 }
551
552 // Queue partial multiplications using both sets of indices
553 non_native_partial_multiplication_witnesses<fr> input1{ .a = a1_indices, .b = b1_indices };
554 non_native_partial_multiplication_witnesses<fr> input2{ .a = a2_indices, .b = b2_indices };
555 builder.queue_partial_non_native_field_multiplication(input1);
556 builder.queue_partial_non_native_field_multiplication(input2);
557
558 // Before finalization, cache has 2 entries
559 EXPECT_EQ(builder.cached_partial_non_native_field_multiplications.size(), 2U);
560
561 // Verify circuit passes
562 EXPECT_TRUE(CircuitChecker::check(builder));
563
564 // Finalize to check deduplication
565 builder.finalize_circuit();
566
567 // After finalization, should be deduplicated to 1 entry
568 EXPECT_EQ(builder.cached_partial_non_native_field_multiplications.size(), 1U);
569
570 // NNF block size should be the same as single multiplication
571 EXPECT_EQ(builder.blocks.nnf.size(), NNF_GATES_PER_PARTIAL_MUL + NNF_ENSURE_NONZERO_PADDING);
572}
573
574// Verifies multiple distinct partial multiplications all produce correct constraints
575TEST_F(UltraCircuitBuilderNonNative, PartialMultiplicationMultipleDistinct)
576{
578
579 const size_t num_multiplications = 5;
580 std::vector<std::array<fr, 4>> a_values(num_multiplications);
581 std::vector<std::array<fr, 4>> b_values(num_multiplications);
582 std::vector<std::array<uint32_t, 2>> results(num_multiplications);
583
584 // Queue multiple distinct partial multiplications
585 for (size_t i = 0; i < num_multiplications; i++) {
586 a_values[i] = random_limbs();
587 b_values[i] = random_limbs();
588 results[i] = create_and_queue_partial_multiplication(builder, a_values[i], b_values[i]);
589 }
590
591 // All entries should be in the cache
592 EXPECT_EQ(builder.cached_partial_non_native_field_multiplications.size(), num_multiplications);
593
594 // Verify circuit passes
595 EXPECT_TRUE(CircuitChecker::check(builder));
596
597 // Finalize to check gate counts
598 builder.finalize_circuit();
599
600 // After finalization, all distinct entries should remain (no deduplication)
601 EXPECT_EQ(builder.cached_partial_non_native_field_multiplications.size(), num_multiplications);
602
603 // NNF block size should scale with number of multiplications
604 EXPECT_EQ(builder.blocks.nnf.size(), num_multiplications * NNF_GATES_PER_PARTIAL_MUL + NNF_ENSURE_NONZERO_PADDING);
605
606 // Verify each result has correct values
607 for (size_t i = 0; i < num_multiplications; i++) {
608 auto [expected_lo_0, expected_hi_1] = compute_expected_partial_products(a_values[i], b_values[i]);
609 EXPECT_EQ(builder.get_variable(results[i][0]), expected_lo_0);
610 EXPECT_EQ(builder.get_variable(results[i][1]), expected_hi_1);
611 }
612}
613
614// Verifies that finalization with empty cache doesn't cause issues
615TEST_F(UltraCircuitBuilderNonNative, ProcessNonNativeFieldMultiplicationsEmptyCache)
616{
618
619 // Create some non-NNF constraints to ensure the circuit isn't completely empty
622 uint32_t a_idx = builder.add_variable(a);
623 uint32_t b_idx = builder.add_variable(b);
624 uint32_t c_idx = builder.add_variable(a + b);
625 builder.create_add_gate({ a_idx, b_idx, c_idx, fr(1), fr(1), fr(-1), fr(0) });
626
627 // No NNF operations queued
628 EXPECT_EQ(builder.cached_partial_non_native_field_multiplications.size(), 0U);
629
630 // Finalization should succeed without issues
631 EXPECT_TRUE(CircuitChecker::check(builder));
632
633 // Finalize to inspect NNF block
634 builder.finalize_circuit();
635
636 // Cache should still be empty
637 EXPECT_EQ(builder.cached_partial_non_native_field_multiplications.size(), 0U);
638
639 // NNF block should only have ensure_nonzero padding (no actual NNF gates)
640 EXPECT_EQ(builder.blocks.nnf.size(), NNF_ENSURE_NONZERO_PADDING);
641}
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
Test suite for UltraCircuitBuilder non-native field methods.
std::tuple< scaled_witness, scaled_witness, fr > add_simple
static std::pair< uint256_t, uint256_t > compute_quotient_remainder(const uint256_t &a, const uint256_t &b, const uint256_t &modulus)
static std::array< uint32_t, 2 > create_and_queue_partial_multiplication(UltraCircuitBuilder &builder, const std::array< fr, 4 > &a_limbs, const std::array< fr, 4 > &b_limbs)
static std::tuple< add_simple, add_simple, add_simple, add_simple, std::tuple< uint32_t, uint32_t, fr > > create_add_sub_inputs(UltraCircuitBuilder &builder, const AddSubData &data)
static std::array< uint32_t, 2 > create_non_native_multiplication(UltraCircuitBuilder &builder, const uint256_t &a, const uint256_t &b, const uint256_t &q, const uint256_t &r, const uint256_t &modulus)
static std::pair< fr, fr > compute_expected_partial_products(const std::array< fr, 4 > &a, const std::array< fr, 4 > &b)
static std::array< fr, 4 > split_into_limbs(const uint512_t &input)
static std::array< uint32_t, 4 > get_limb_witness_indices(UltraCircuitBuilder &builder, const std::array< fr, 4 > &limbs)
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
static constexpr size_t DEFAULT_NON_NATIVE_FIELD_LIMB_BITS
constexpr uint64_t get_msb() const
constexpr uintx slice(const uint64_t start, const uint64_t end) const
Definition uintx.hpp:81
AluTraceBuilder builder
Definition alu.test.cpp:124
const std::vector< MemoryValue > data
FF a
FF b
AvmProvingInputs inputs
uintx< uint256_t > uint512_t
Definition uintx.hpp:309
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
field< Bn254FrParams > fr
Definition fr.hpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static constexpr uint256_t modulus
static field random_element(numeric::RNG *engine=nullptr) noexcept
TEST_F(UltraCircuitBuilderNonNative, Multiplication)