|
4 | 4 |
|
5 | 5 | #include "acir_format.hpp" |
6 | 6 | #include "acir_format_mocks.hpp" |
| 7 | +#include "acir_to_constraint_buf.hpp" |
7 | 8 | #include "barretenberg/common/streams.hpp" |
8 | 9 | #include "barretenberg/op_queue/ecc_op_queue.hpp" |
9 | 10 |
|
@@ -341,3 +342,82 @@ TEST_F(AcirFormatTests, TestBigAdd) |
341 | 342 |
|
342 | 343 | EXPECT_TRUE(CircuitChecker::check(builder)); |
343 | 344 | } |
| 345 | + |
| 346 | +// Helper function to convert a uint256_t to a 32-byte vector in big-endian format |
| 347 | +std::vector<uint8_t> to_bytes_be(uint256_t value) |
| 348 | +{ |
| 349 | + std::vector<uint8_t> bytes(32, 0); |
| 350 | + for (size_t i = 0; i < 32; i++) { |
| 351 | + bytes[31 - i] = static_cast<uint8_t>(value & 0xFF); |
| 352 | + value >>= 8; |
| 353 | + } |
| 354 | + return bytes; |
| 355 | +} |
| 356 | + |
| 357 | +/** |
| 358 | + * @brief Test for bug fix where expressions with distinct witnesses requiring more than one width-4 gate |
| 359 | + * were incorrectly processed when they initially appeared to fit in width-3 gates |
| 360 | + * |
| 361 | + * @details This test verifies the fix for a bug in handle_arithmetic where an expression with: |
| 362 | + * - 1 mul term using witnesses (w0 * w1) |
| 363 | + * - 3 additional linear terms using distinct witnesses (w2, w3, w4) |
| 364 | + * |
| 365 | + * Such expressions have ≤3 linear combinations and ≤1 mul term, appearing to fit in a |
| 366 | + * poly_triple (width-3) gate. However, with all 5 witnesses distinct, serialize_arithmetic_gate |
| 367 | + * correctly returns all zeros, indicating it cannot fit in a width-3 gate. |
| 368 | + * |
| 369 | + * The bug: old code would check if poly_triple was all zeros, and if so, directly add to |
| 370 | + * quad_constraints via serialize_mul_quad_gate. But it did this inside the initial |
| 371 | + * might_fit_in_polytriple check, so it would never properly go through the mul_quad processing |
| 372 | + * logic that handles the general case with >4 witnesses. |
| 373 | + * |
| 374 | + * The fix: now uses a needs_to_be_parsed_as_mul_quad flag that is set when poly_triple fails, |
| 375 | + * and processes through the proper mul_quad logic path, which splits into multiple gates. |
| 376 | + * |
| 377 | + * Expression: w0 * w1 + w2 + w3 + w4 = 10 |
| 378 | + * With witnesses: w0=0, w1=1, w2=2, w3=3, w4=4 |
| 379 | + * Evaluation: 0*1 + 2 + 3 + 4 = 9, but we set q_c = -9, so constraint is: 9 - 9 = 0 |
| 380 | + */ |
| 381 | +TEST_F(AcirFormatTests, TestArithmeticGateWithDistinctWitnessesRegression) |
| 382 | +{ |
| 383 | + // Create an ACIR expression: w0 * w1 + w2 + w3 + w4 - 9 = 0 |
| 384 | + // This has 1 mul term and 3 linear terms with all 5 distinct witnesses (requires multiple width-4 gates) |
| 385 | + Acir::Expression expr{ .mul_terms = { std::make_tuple( |
| 386 | + to_bytes_be(1), Acir::Witness{ .value = 0 }, Acir::Witness{ .value = 1 }) }, |
| 387 | + .linear_combinations = { std::make_tuple(to_bytes_be(1), Acir::Witness{ .value = 2 }), |
| 388 | + std::make_tuple(to_bytes_be(1), Acir::Witness{ .value = 3 }), |
| 389 | + std::make_tuple(to_bytes_be(1), Acir::Witness{ .value = 4 }) }, |
| 390 | + .q_c = to_bytes_be(static_cast<uint256_t>(fr(-9))) }; |
| 391 | + |
| 392 | + Acir::Opcode::AssertZero assert_zero{ .value = expr }; |
| 393 | + |
| 394 | + // Create an ACIR circuit with this opcode |
| 395 | + Acir::Circuit circuit{ |
| 396 | + .current_witness_index = 5, |
| 397 | + .opcodes = { Acir::Opcode{ .value = assert_zero } }, |
| 398 | + .return_values = {}, |
| 399 | + }; |
| 400 | + |
| 401 | + Acir::Program program{ .functions = { circuit } }; |
| 402 | + |
| 403 | + // Serialize the program to bytes |
| 404 | + auto program_bytes = program.bincodeSerialize(); |
| 405 | + |
| 406 | + // Process through circuit_buf_to_acir_format (this calls handle_arithmetic internally) |
| 407 | + AcirFormat constraint_system = circuit_buf_to_acir_format(std::move(program_bytes)); |
| 408 | + |
| 409 | + // The key assertion: this expression should end up in big_quad_constraints, not poly_triple_constraints |
| 410 | + // or single quad_constraints, because it needs 5 witness slots (all distinct) |
| 411 | + EXPECT_EQ(constraint_system.poly_triple_constraints.size(), 0); |
| 412 | + EXPECT_EQ(constraint_system.quad_constraints.size(), 0); |
| 413 | + EXPECT_EQ(constraint_system.big_quad_constraints.size(), 1); |
| 414 | + |
| 415 | + // Now verify the constraint system with valid witness assignments |
| 416 | + // We need: w0 * w1 + w2 + w3 + w4 = 9 |
| 417 | + // Use values: w0=0, w1=1, w2=2, w3=3, w4=4, so 0*1 + 2 + 3 + 4 = 9 |
| 418 | + WitnessVector witness{ 0, 1, 2, 3, 4 }; |
| 419 | + AcirProgram acir_program{ constraint_system, witness }; |
| 420 | + auto builder = create_circuit(acir_program); |
| 421 | + |
| 422 | + EXPECT_TRUE(CircuitChecker::check(builder)); |
| 423 | +} |
0 commit comments