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.gate_selector_for(bb::GateKind::Lookup)[i],
81 fr(1)); // gate selector should be "on"
82 EXPECT_EQ(builder.blocks.lookup.q_1()[i], fr(0)); // unused in lookup gates
83 EXPECT_EQ(builder.blocks.lookup.q_4()[i], fr(0)); // unused in lookup gates
84 }
85
86 EXPECT_TRUE(CircuitChecker::check(builder));
87}
88
89// Verifies that different tables get unique indices
90TEST_F(UltraCircuitBuilderLookup, DifferentTablesGetUniqueIndices)
91{
93
94 // Specify three different table IDs
95 const auto table_id1 = plookup::BasicTableId::UINT_XOR_SLICE_6_ROTATE_0;
96 const auto table_id2 = plookup::BasicTableId::UINT_XOR_SLICE_2_ROTATE_0;
97 const auto table_id3 = plookup::BasicTableId::UINT_AND_SLICE_6_ROTATE_0;
98
99 // Construct four tables, three unique and one duplicate
100 auto& table1 = builder.get_table(table_id1);
101 auto& table2 = builder.get_table(table_id2);
102 auto& table1_again = builder.get_table(table_id1); // duplicate of table1
103 auto& table3 = builder.get_table(table_id3);
104
105 // table1 and table1_again should be the same reference
106 EXPECT_EQ(&table1, &table1_again);
107
108 // Table IDs should be set correctly
109 EXPECT_EQ(table1.id, table_id1);
110 EXPECT_EQ(table2.id, table_id2);
111 EXPECT_EQ(table1_again.id, table_id1);
112 EXPECT_EQ(table3.id, table_id3);
113
114 // Tables should have `table_index` based on order of creation
115 EXPECT_EQ(table1.table_index, 0UL);
116 EXPECT_EQ(table2.table_index, 1UL);
117 EXPECT_EQ(table1_again.table_index, 0UL);
118 EXPECT_EQ(table3.table_index, 2UL);
119
120 // Exactly three different tables should have been created
121 EXPECT_EQ(builder.get_num_lookup_tables(), 3UL);
122}
123
124// Verifies correct behavior when key_b_index is not provided (2-to-1 lookup without second index)
126{
128
129 // HONK_DUMMY_MULTI is a 2-to-1 lookup (two keys, one result)
130 // Tables only contain entries for values 0 and 1 (base = 1 << 1)
131 const fr a_value(1);
132 const fr b_value(0);
133 const auto a_idx = builder.add_variable(a_value);
134 // Not providing b_idx - it will be created from accumulators
135
136 const auto accumulators =
137 plookup::get_lookup_accumulators(plookup::MultiTableId::HONK_DUMMY_MULTI, a_value, b_value, true);
138 const auto result = builder.create_gates_from_plookup_accumulators(
139 plookup::MultiTableId::HONK_DUMMY_MULTI, accumulators, a_idx, std::nullopt);
140
141 // First lookup should reuse a_idx for C1
142 EXPECT_EQ(result[ColumnIdx::C1][0], a_idx);
143
144 // C2 and C3 should be newly created variables
145 EXPECT_NE(result[ColumnIdx::C2][0], a_idx);
146 EXPECT_NE(result[ColumnIdx::C3][0], a_idx);
147
148 EXPECT_TRUE(CircuitChecker::check(builder));
149}
150
151// Verifies that lookup entries are recorded in the table's lookup_gates vector
152TEST_F(UltraCircuitBuilderLookup, LookupEntriesRecorded)
153{
155
156 const fr a_value(33);
157 const fr b_value(44);
158 const auto a_idx = builder.add_variable(a_value);
159 const auto b_idx = builder.add_variable(b_value);
160
161 const auto accumulators =
162 plookup::get_lookup_accumulators(plookup::MultiTableId::UINT32_XOR, a_value, b_value, true);
163
164 const auto& multi_table = plookup::get_multitable(plookup::MultiTableId::UINT32_XOR);
165
166 // Get unique table IDs and record their initial sizes
167 // Note: UINT32_XOR uses UINT_XOR_SLICE_6_ROTATE_0 five times and UINT_XOR_SLICE_2_ROTATE_0 once
170
171 for (const auto& table_id : multi_table.basic_table_ids) {
172 if (initial_sizes.find(table_id) == initial_sizes.end()) {
173 auto& table = builder.get_table(table_id);
174 initial_sizes[table_id] = table.lookup_gates.size();
175 expected_additions[table_id] = 0;
176 }
177 expected_additions[table_id]++;
178 }
179
180 builder.create_gates_from_plookup_accumulators(plookup::MultiTableId::UINT32_XOR, accumulators, a_idx, b_idx);
181
182 // Check that each unique table received the correct number of new lookup entries
183 for (const auto& [table_id, initial_size] : initial_sizes) {
184 auto& table = builder.get_table(table_id);
185 EXPECT_EQ(table.lookup_gates.size(), initial_size + expected_additions[table_id]);
186 }
187
188 EXPECT_TRUE(CircuitChecker::check(builder));
189}
190
191// Verifies that corrupting any accumulator position in any column causes circuit check to fail
192TEST_F(UltraCircuitBuilderLookup, BadAccumulatorFaiure)
193{
194 auto test_corrupt_accumulator = [](ColumnIdx column, size_t position) {
196
197 const fr a_value(123);
198 const fr b_value(456);
199 const auto a_idx = builder.add_variable(a_value);
200 const auto b_idx = builder.add_variable(b_value);
201
202 // Get valid accumulators
203 auto accumulators = plookup::get_lookup_accumulators(plookup::MultiTableId::UINT32_XOR, a_value, b_value, true);
204
205 // Corrupt the specified accumulator entry
206 accumulators[column][position] += fr(1);
207
208 builder.create_gates_from_plookup_accumulators(plookup::MultiTableId::UINT32_XOR, accumulators, a_idx, b_idx);
209
210 // Circuit should fail because the corrupted accumulator doesn't match the table
211 EXPECT_FALSE(CircuitChecker::check(builder));
212 };
213
214 // UINT32_XOR has 6 lookups (five 6-bit tables, one 2-bit table)
215 const size_t num_lookups = 6;
216
217 // Test corrupting each position in each column
218 for (size_t i = 0; i < num_lookups; ++i) {
219 // Note: C1[0] and C2[0] are not tested because the first lookup gate reuses the existing
220 // witness indices (key_a_index and key_b_index) rather than creating new witnesses from
221 // accumulators[C1][0] and accumulators[C2][0]
222 if (i > 0) {
223 test_corrupt_accumulator(ColumnIdx::C1, i);
224 test_corrupt_accumulator(ColumnIdx::C2, i);
225 }
226 // C3 is always created from accumulators, so test all positions
227 test_corrupt_accumulator(ColumnIdx::C3, i);
228 }
229}
230
231// Verifies that invalid input witness values (C1[0] and C2[0]) cause circuit check to fail
232TEST_F(UltraCircuitBuilderLookup, InvalidInputWitnessFailure)
233{
234 const fr a_value(123);
235 const fr b_value(456);
236
237 // Compute accumulators based on the genuine values
238 const auto accumulators =
239 plookup::get_lookup_accumulators(plookup::MultiTableId::UINT32_XOR, a_value, b_value, true);
240
241 // Test with wrong witness value for key_a (first input, reused as C1[0])
242 {
244
245 // Create witness with bad value for first input
246 const fr bad_a_value(666);
247 const auto bad_a_idx = builder.add_variable(bad_a_value);
248 const auto b_idx = builder.add_variable(b_value);
249
250 builder.create_gates_from_plookup_accumulators(
251 plookup::MultiTableId::UINT32_XOR, accumulators, bad_a_idx, b_idx);
252
253 // Circuit should fail because witness at a_idx doesn't match what accumulators expect
254 EXPECT_FALSE(CircuitChecker::check(builder));
255 }
256
257 // Test with wrong witness value for key_b (second input, reused as C2[0])
258 {
260
261 // Create witness with bad value for second input
262 const fr bad_b_value(666);
263 const auto a_idx = builder.add_variable(a_value);
264 const auto bad_b_idx = builder.add_variable(bad_b_value);
265
266 builder.create_gates_from_plookup_accumulators(
267 plookup::MultiTableId::UINT32_XOR, accumulators, a_idx, bad_b_idx);
268
269 // Circuit should fail because witness at b_idx doesn't match what accumulators expect
270 EXPECT_FALSE(CircuitChecker::check(builder));
271 }
272}
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:155
UltraCircuitBuilder_< UltraExecutionTraceBlocks > UltraCircuitBuilder
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
TEST_F(UltraCircuitBuilderLookup, BasicLookup)