Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ultra_circuit_builder_lookup.test.cpp
Go to the documentation of this file.
4
5#include <gtest/gtest.h>
6#include <unordered_map>
7
8using namespace bb;
9
10class UltraCircuitBuilderLookup : public ::testing::Test {
11 protected:
14};
15
16// Verifies that a valid lookup operation creates the expected number of gates and passes circuit check
18{
20
21 // UINT32_XOR decomposes into 6 lookups: five 6-bit tables, one 2-bit table
22 const fr a_value(42);
23 const fr b_value(17);
24 const auto a_idx = builder.add_variable(a_value);
25 const auto b_idx = builder.add_variable(b_value);
26
27 const auto accumulators =
28 plookup::get_lookup_accumulators(plookup::MultiTableId::UINT32_XOR, a_value, b_value, true);
29 const auto result =
30 builder.create_gates_from_plookup_accumulators(plookup::MultiTableId::UINT32_XOR, accumulators, a_idx, b_idx);
31
32 // First lookup should reuse input indices
33 EXPECT_EQ(result[ColumnIdx::C1][0], a_idx);
34 EXPECT_EQ(result[ColumnIdx::C2][0], b_idx);
35
36 // Check builder state
37 EXPECT_EQ(result[ColumnIdx::C1].size(), 6UL);
38 EXPECT_EQ(result[ColumnIdx::C2].size(), 6UL);
39 EXPECT_EQ(result[ColumnIdx::C3].size(), 6UL);
40 EXPECT_EQ(builder.blocks.lookup.size(), 6UL);
41
42 // Check circuit satisfaction
43 EXPECT_TRUE(CircuitChecker::check(builder));
44}
45
46// Verifies that step size coefficients are set correctly for each gate in a multi-table lookup
47TEST_F(UltraCircuitBuilderLookup, StepSizeCoefficients)
48{
50
51 const fr a_value(7);
52 const fr b_value(14);
53 const auto a_idx = builder.add_variable(a_value);
54 const auto b_idx = builder.add_variable(b_value);
55
56 const auto accumulators =
57 plookup::get_lookup_accumulators(plookup::MultiTableId::UINT32_XOR, a_value, b_value, true);
58 builder.create_gates_from_plookup_accumulators(plookup::MultiTableId::UINT32_XOR, accumulators, a_idx, b_idx);
59
60 const auto& multi_table = plookup::get_multitable(plookup::MultiTableId::UINT32_XOR);
61 const size_t num_lookups = multi_table.column_1_step_sizes.size();
62
63 // Check that step sizes have been populated correctly in the the corresponding selectors
64 for (size_t i = 0; i < num_lookups - 1; ++i) {
65 EXPECT_EQ(builder.blocks.lookup.q_2()[i], -multi_table.column_1_step_sizes[i + 1]);
66 EXPECT_EQ(builder.blocks.lookup.q_m()[i], -multi_table.column_2_step_sizes[i + 1]);
67 EXPECT_EQ(builder.blocks.lookup.q_c()[i], -multi_table.column_3_step_sizes[i + 1]);
68 }
69
70 // Check last gate has zero step sizes
71 const size_t last_idx = num_lookups - 1;
72 EXPECT_EQ(builder.blocks.lookup.q_2()[last_idx], fr(0));
73 EXPECT_EQ(builder.blocks.lookup.q_m()[last_idx], fr(0));
74 EXPECT_EQ(builder.blocks.lookup.q_c()[last_idx], fr(0));
75
76 // Check that remaining selectors are set correctly
77 for (size_t i = 0; i < num_lookups; ++i) {
78 const auto& table = builder.get_table(multi_table.basic_table_ids[i]);
79 EXPECT_EQ(builder.blocks.lookup.q_3()[i], fr(table.table_index)); // unique table identifier
80 EXPECT_EQ(builder.blocks.lookup.q_lookup()[i], fr(1)); // gate selector should be "on"
81 EXPECT_EQ(builder.blocks.lookup.q_1()[i], fr(0)); // unused in lookup gates
82 EXPECT_EQ(builder.blocks.lookup.q_4()[i], fr(0)); // unused in lookup gates
83 }
84
85 EXPECT_TRUE(CircuitChecker::check(builder));
86}
87
88// Verifies that different tables get unique indices
89TEST_F(UltraCircuitBuilderLookup, DifferentTablesGetUniqueIndices)
90{
92
93 // Specify three different table IDs
94 const auto table_id1 = plookup::BasicTableId::UINT_XOR_SLICE_6_ROTATE_0;
95 const auto table_id2 = plookup::BasicTableId::UINT_XOR_SLICE_2_ROTATE_0;
96 const auto table_id3 = plookup::BasicTableId::UINT_AND_SLICE_6_ROTATE_0;
97
98 // Construct four tables, three unique and one duplicate
99 auto& table1 = builder.get_table(table_id1);
100 auto& table2 = builder.get_table(table_id2);
101 auto& table1_again = builder.get_table(table_id1); // duplicate of table1
102 auto& table3 = builder.get_table(table_id3);
103
104 // table1 and table1_again should be the same reference
105 EXPECT_EQ(&table1, &table1_again);
106
107 // Table IDs should be set correctly
108 EXPECT_EQ(table1.id, table_id1);
109 EXPECT_EQ(table2.id, table_id2);
110 EXPECT_EQ(table1_again.id, table_id1);
111 EXPECT_EQ(table3.id, table_id3);
112
113 // Tables should have `table_index` based on order of creation
114 EXPECT_EQ(table1.table_index, 0UL);
115 EXPECT_EQ(table2.table_index, 1UL);
116 EXPECT_EQ(table1_again.table_index, 0UL);
117 EXPECT_EQ(table3.table_index, 2UL);
118
119 // Exactly three different tables should have been created
120 EXPECT_EQ(builder.get_num_lookup_tables(), 3UL);
121}
122
123// Verifies correct behavior when key_b_index is not provided (2-to-1 lookup without second index)
125{
127
128 // HONK_DUMMY_MULTI is a 2-to-1 lookup (two keys, one result)
129 // Tables only contain entries for values 0 and 1 (base = 1 << 1)
130 const fr a_value(1);
131 const fr b_value(0);
132 const auto a_idx = builder.add_variable(a_value);
133 // Not providing b_idx - it will be created from accumulators
134
135 const auto accumulators =
136 plookup::get_lookup_accumulators(plookup::MultiTableId::HONK_DUMMY_MULTI, a_value, b_value, true);
137 const auto result = builder.create_gates_from_plookup_accumulators(
138 plookup::MultiTableId::HONK_DUMMY_MULTI, accumulators, a_idx, std::nullopt);
139
140 // First lookup should reuse a_idx for C1
141 EXPECT_EQ(result[ColumnIdx::C1][0], a_idx);
142
143 // C2 and C3 should be newly created variables
144 EXPECT_NE(result[ColumnIdx::C2][0], a_idx);
145 EXPECT_NE(result[ColumnIdx::C3][0], a_idx);
146
147 EXPECT_TRUE(CircuitChecker::check(builder));
148}
149
150// Verifies that lookup entries are recorded in the table's lookup_gates vector
151TEST_F(UltraCircuitBuilderLookup, LookupEntriesRecorded)
152{
154
155 const fr a_value(33);
156 const fr b_value(44);
157 const auto a_idx = builder.add_variable(a_value);
158 const auto b_idx = builder.add_variable(b_value);
159
160 const auto accumulators =
161 plookup::get_lookup_accumulators(plookup::MultiTableId::UINT32_XOR, a_value, b_value, true);
162
163 const auto& multi_table = plookup::get_multitable(plookup::MultiTableId::UINT32_XOR);
164
165 // Get unique table IDs and record their initial sizes
166 // Note: UINT32_XOR uses UINT_XOR_SLICE_6_ROTATE_0 five times and UINT_XOR_SLICE_2_ROTATE_0 once
169
170 for (const auto& table_id : multi_table.basic_table_ids) {
171 if (initial_sizes.find(table_id) == initial_sizes.end()) {
172 auto& table = builder.get_table(table_id);
173 initial_sizes[table_id] = table.lookup_gates.size();
174 expected_additions[table_id] = 0;
175 }
176 expected_additions[table_id]++;
177 }
178
179 builder.create_gates_from_plookup_accumulators(plookup::MultiTableId::UINT32_XOR, accumulators, a_idx, b_idx);
180
181 // Check that each unique table received the correct number of new lookup entries
182 for (const auto& [table_id, initial_size] : initial_sizes) {
183 auto& table = builder.get_table(table_id);
184 EXPECT_EQ(table.lookup_gates.size(), initial_size + expected_additions[table_id]);
185 }
186
187 EXPECT_TRUE(CircuitChecker::check(builder));
188}
189
190// Verifies that corrupting any accumulator position in any column causes circuit check to fail
191TEST_F(UltraCircuitBuilderLookup, BadAccumulatorFaiure)
192{
193 auto test_corrupt_accumulator = [](ColumnIdx column, size_t position) {
195
196 const fr a_value(123);
197 const fr b_value(456);
198 const auto a_idx = builder.add_variable(a_value);
199 const auto b_idx = builder.add_variable(b_value);
200
201 // Get valid accumulators
202 auto accumulators = plookup::get_lookup_accumulators(plookup::MultiTableId::UINT32_XOR, a_value, b_value, true);
203
204 // Corrupt the specified accumulator entry
205 accumulators[column][position] += fr(1);
206
207 builder.create_gates_from_plookup_accumulators(plookup::MultiTableId::UINT32_XOR, accumulators, a_idx, b_idx);
208
209 // Circuit should fail because the corrupted accumulator doesn't match the table
210 EXPECT_FALSE(CircuitChecker::check(builder));
211 };
212
213 // UINT32_XOR has 6 lookups (five 6-bit tables, one 2-bit table)
214 const size_t num_lookups = 6;
215
216 // Test corrupting each position in each column
217 for (size_t i = 0; i < num_lookups; ++i) {
218 // Note: C1[0] and C2[0] are not tested because the first lookup gate reuses the existing
219 // witness indices (key_a_index and key_b_index) rather than creating new witnesses from
220 // accumulators[C1][0] and accumulators[C2][0]
221 if (i > 0) {
222 test_corrupt_accumulator(ColumnIdx::C1, i);
223 test_corrupt_accumulator(ColumnIdx::C2, i);
224 }
225 // C3 is always created from accumulators, so test all positions
226 test_corrupt_accumulator(ColumnIdx::C3, i);
227 }
228}
229
230// Verifies that invalid input witness values (C1[0] and C2[0]) cause circuit check to fail
231TEST_F(UltraCircuitBuilderLookup, InvalidInputWitnessFailure)
232{
233 const fr a_value(123);
234 const fr b_value(456);
235
236 // Compute accumulators based on the genuine values
237 const auto accumulators =
238 plookup::get_lookup_accumulators(plookup::MultiTableId::UINT32_XOR, a_value, b_value, true);
239
240 // Test with wrong witness value for key_a (first input, reused as C1[0])
241 {
243
244 // Create witness with bad value for first input
245 const fr bad_a_value(666);
246 const auto bad_a_idx = builder.add_variable(bad_a_value);
247 const auto b_idx = builder.add_variable(b_value);
248
249 builder.create_gates_from_plookup_accumulators(
250 plookup::MultiTableId::UINT32_XOR, accumulators, bad_a_idx, b_idx);
251
252 // Circuit should fail because witness at a_idx doesn't match what accumulators expect
253 EXPECT_FALSE(CircuitChecker::check(builder));
254 }
255
256 // Test with wrong witness value for key_b (second input, reused as C2[0])
257 {
259
260 // Create witness with bad value for second input
261 const fr bad_b_value(666);
262 const auto a_idx = builder.add_variable(a_value);
263 const auto bad_b_idx = builder.add_variable(bad_b_value);
264
265 builder.create_gates_from_plookup_accumulators(
266 plookup::MultiTableId::UINT32_XOR, accumulators, a_idx, bad_b_idx);
267
268 // Circuit should fail because witness at b_idx doesn't match what accumulators expect
269 EXPECT_FALSE(CircuitChecker::check(builder));
270 }
271}
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
AluTraceBuilder builder
Definition alu.test.cpp:124
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.
const MultiTable & get_multitable(const MultiTableId id)
Return the multitable with the provided ID; construct all MultiTables if not constructed already.
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
field< Bn254FrParams > fr
Definition fr.hpp:174
UltraCircuitBuilder_< UltraExecutionTraceBlocks > UltraCircuitBuilder
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
TEST_F(UltraCircuitBuilderLookup, BasicLookup)