Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ cd ..
# - Generate a hash for versioning: sha256sum bb-chonk-inputs.tar.gz
# - Upload the compressed results: aws s3 cp bb-chonk-inputs.tar.gz s3://aztec-ci-artifacts/protocol/bb-chonk-inputs-[hash(0:8)].tar.gz
# Note: In case of the "Test suite failed to run ... Unexpected token 'with' " error, need to run: docker pull aztecprotocol/build:3.0
pinned_short_hash="bd98634a"
pinned_chonk_inputs_url="https://aztec-ci-artifacts.s3.us-east-2.amazonaws.com/protocol/bb-chonk-inputs-${pinned_short_hash}.tar.gz"
pinned_short_hash="abdb6bae"
pinned_chonk_inputs_url="https://aztec-ci-artifacts.s3.us-east-2.amazonaws.com/protocol/bb-civc-inputs-${pinned_short_hash}.tar.gz"

function compress_and_upload {
# 1) Compress the results
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "acir_format.hpp"
#include "acir_format_mocks.hpp"
#include "acir_to_constraint_buf.hpp"
#include "barretenberg/common/streams.hpp"
#include "barretenberg/op_queue/ecc_op_queue.hpp"

Expand Down Expand Up @@ -341,3 +342,82 @@ TEST_F(AcirFormatTests, TestBigAdd)

EXPECT_TRUE(CircuitChecker::check(builder));
}

// Helper function to convert a uint256_t to a 32-byte vector in big-endian format
std::vector<uint8_t> to_bytes_be(uint256_t value)
{
std::vector<uint8_t> bytes(32, 0);
for (size_t i = 0; i < 32; i++) {
bytes[31 - i] = static_cast<uint8_t>(value & 0xFF);
value >>= 8;
}
return bytes;
}

/**
* @brief Test for bug fix where expressions with distinct witnesses requiring more than one width-4 gate
* were incorrectly processed when they initially appeared to fit in width-3 gates
*
* @details This test verifies the fix for a bug in handle_arithmetic where an expression with:
* - 1 mul term using witnesses (w0 * w1)
* - 3 additional linear terms using distinct witnesses (w2, w3, w4)
*
* Such expressions have ≤3 linear combinations and ≤1 mul term, appearing to fit in a
* poly_triple (width-3) gate. However, with all 5 witnesses distinct, serialize_arithmetic_gate
* correctly returns all zeros, indicating it cannot fit in a width-3 gate.
*
* The bug: old code would check if poly_triple was all zeros, and if so, directly add to
* quad_constraints via serialize_mul_quad_gate. But it did this inside the initial
* might_fit_in_polytriple check, so it would never properly go through the mul_quad processing
* logic that handles the general case with >4 witnesses.
*
* The fix: now uses a needs_to_be_parsed_as_mul_quad flag that is set when poly_triple fails,
* and processes through the proper mul_quad logic path, which splits into multiple gates.
*
* Expression: w0 * w1 + w2 + w3 + w4 = 10
* With witnesses: w0=0, w1=1, w2=2, w3=3, w4=4
* Evaluation: 0*1 + 2 + 3 + 4 = 9, but we set q_c = -9, so constraint is: 9 - 9 = 0
*/
TEST_F(AcirFormatTests, TestArithmeticGateWithDistinctWitnessesRegression)
{
// Create an ACIR expression: w0 * w1 + w2 + w3 + w4 - 9 = 0
// This has 1 mul term and 3 linear terms with all 5 distinct witnesses (requires multiple width-4 gates)
Acir::Expression expr{ .mul_terms = { std::make_tuple(
to_bytes_be(1), Acir::Witness{ .value = 0 }, Acir::Witness{ .value = 1 }) },
.linear_combinations = { std::make_tuple(to_bytes_be(1), Acir::Witness{ .value = 2 }),
std::make_tuple(to_bytes_be(1), Acir::Witness{ .value = 3 }),
std::make_tuple(to_bytes_be(1), Acir::Witness{ .value = 4 }) },
.q_c = to_bytes_be(static_cast<uint256_t>(fr(-9))) };

Acir::Opcode::AssertZero assert_zero{ .value = expr };

// Create an ACIR circuit with this opcode
Acir::Circuit circuit{
.current_witness_index = 5,
.opcodes = { Acir::Opcode{ .value = assert_zero } },
.return_values = {},
};

Acir::Program program{ .functions = { circuit } };

// Serialize the program to bytes
auto program_bytes = program.bincodeSerialize();

// Process through circuit_buf_to_acir_format (this calls handle_arithmetic internally)
AcirFormat constraint_system = circuit_buf_to_acir_format(std::move(program_bytes));

// The key assertion: this expression should end up in big_quad_constraints, not poly_triple_constraints
// or single quad_constraints, because it needs 5 witness slots (all distinct)
EXPECT_EQ(constraint_system.poly_triple_constraints.size(), 0);
EXPECT_EQ(constraint_system.quad_constraints.size(), 0);
EXPECT_EQ(constraint_system.big_quad_constraints.size(), 1);

// Now verify the constraint system with valid witness assignments
// We need: w0 * w1 + w2 + w3 + w4 = 9
// Use values: w0=0, w1=1, w2=2, w3=3, w4=4, so 0*1 + 2 + 3 + 4 = 9
WitnessVector witness{ 0, 1, 2, 3, 4 };
AcirProgram acir_program{ constraint_system, witness };
auto builder = create_circuit(acir_program);

EXPECT_TRUE(CircuitChecker::check(builder));
}
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,9 @@ std::pair<uint32_t, uint32_t> is_assert_equal(Acir::Opcode::AssertZero const& ar
void handle_arithmetic(Acir::Opcode::AssertZero const& arg, AcirFormat& af, size_t opcode_index)
{
// If the expression fits in a polytriple, we use it.
if (arg.value.linear_combinations.size() <= 3 && arg.value.mul_terms.size() <= 1) {
bool might_fit_in_polytriple = arg.value.linear_combinations.size() <= 3 && arg.value.mul_terms.size() <= 1;
bool needs_to_be_parsed_as_mul_quad = !might_fit_in_polytriple;
if (might_fit_in_polytriple) {
poly_triple pt = serialize_arithmetic_gate(arg.value);

auto assert_equal = is_assert_equal(arg, pt, af);
Expand Down Expand Up @@ -502,15 +504,14 @@ void handle_arithmetic(Acir::Opcode::AssertZero const& arg, AcirFormat& af, size
// gate. This is the case if the linear terms are all distinct witness from the multiplication term. In that
// case, the serialize_arithmetic_gate() function will return a poly_triple with all 0's, and we use a width-4
// gate instead. We could probably always use a width-4 gate in fact.
if (pt == poly_triple{ 0, 0, 0, 0, 0, 0, 0, 0 }) {
af.quad_constraints.push_back(serialize_mul_quad_gate(arg.value));
af.original_opcode_indices.quad_constraints.push_back(opcode_index);

} else {
if (pt != poly_triple{ 0, 0, 0, 0, 0, 0, 0, 0 }) {
af.poly_triple_constraints.push_back(pt);
af.original_opcode_indices.poly_triple_constraints.push_back(opcode_index);
} else {
needs_to_be_parsed_as_mul_quad = true;
}
} else {
}
if (needs_to_be_parsed_as_mul_quad) {
std::vector<mul_quad_<fr>> mul_quads;
// We try to use a single mul_quad gate to represent the expression.
if (arg.value.mul_terms.size() <= 1) {
Expand Down
Loading