Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ecc_op_queue.test.cpp
Go to the documentation of this file.
2#include <gtest/gtest.h>
3
4using namespace bb;
5
7 public:
12
13 // Perform some basic interactions with the ECC op queue to mock the behavior of a single circuit
14 static void populate_an_arbitrary_subtable_of_ops(const std::shared_ptr<bb::ECCOpQueue>& op_queue,
15 bool initialize = true)
16 {
17 auto P1 = G1::random_element();
18 auto P2 = G1::random_element();
19 auto z = Fr::random_element();
20
21 if (initialize) {
22 op_queue->initialize_new_subtable();
23 }
24 op_queue->add_accumulate(P1);
25 op_queue->mul_accumulate(P2, z);
26 op_queue->eq_and_reset();
27 }
28
33 static void check_table_column_polynomials(const std::shared_ptr<bb::ECCOpQueue>& op_queue,
34 MergeSettings settings,
35 std::optional<size_t> ultra_fixed_offset = std::nullopt)
36 {
37 // Construct column polynomials corresponding to the full table (T), the previous table (T_prev), and the
38 // current subtable (t_current)
39 auto table_polynomials = op_queue->construct_ultra_ops_table_columns();
40 auto prev_table_polynomials = op_queue->construct_previous_ultra_ops_table_columns();
41 auto subtable_polynomials = op_queue->construct_current_ultra_ops_subtable_columns();
42
43 // Check T(x) = t_current(x) + x^k * T_prev(x) at a single random challenge point
44 Fr eval_challenge = Fr::random_element();
45 for (auto [table_poly, prev_table_poly, subtable_poly] :
46 zip_view(table_polynomials, prev_table_polynomials, subtable_polynomials)) {
47 const Fr table_eval = table_poly.evaluate(eval_challenge); // T(x)
48 // Check that the previous table polynomial is constructed correctly according to the merge settings by
49 // checking the identity at a single point
50 if (settings == MergeSettings::PREPEND) {
51 // T(x) = t_current(x) + x^k * T_prev(x), where k is the size of the current subtable
52 const size_t current_subtable_size = op_queue->get_current_ultra_ops_subtable_num_rows(); // k
53 const Fr subtable_eval = subtable_poly.evaluate(eval_challenge); // t_current(x)
54 const Fr shifted_previous_table_eval = prev_table_poly.evaluate(eval_challenge) *
55 eval_challenge.pow(current_subtable_size); // x^k * T_prev(x)
56 EXPECT_EQ(table_eval, subtable_eval + shifted_previous_table_eval);
57 } else {
58 // APPEND merge performs concatenation directly to end of previous table or at a specified fixed offset
59 const size_t prev_table_size = op_queue->get_previous_ultra_ops_table_num_rows(); // k
60 const size_t shift_magnitude = ultra_fixed_offset.has_value()
61 ? ultra_fixed_offset.value() * bb::UltraEccOpsTable::NUM_ROWS_PER_OP
62 : prev_table_size; // k
63 // T(x) = T_prev(x) + x^k * t_current(x), where k is the shift magnitude
64 const Fr prev_table_eval = prev_table_poly.evaluate(eval_challenge); // T_prev(x)
65 const Fr shifted_subtable_eval =
66 subtable_poly.evaluate(eval_challenge) * eval_challenge.pow(shift_magnitude); // x^k * t_current(x)
67 EXPECT_EQ(table_eval, shifted_subtable_eval + prev_table_eval);
68 }
69 }
70 }
71
77 static void check_opcode_consistency_with_eccvm(const std::shared_ptr<bb::ECCOpQueue>& op_queue)
78 {
79 auto ultra_table = op_queue->get_ultra_ops();
80 auto eccvm_table = op_queue->get_eccvm_ops();
81
82 size_t j = 0;
83 for (const auto& ultra_op : ultra_table) {
84 if (ultra_op.op_code.value() == 0) {
85 continue;
86 }
87 EXPECT_EQ(ultra_op.op_code.value(), eccvm_table[j++].op_code.value());
88 }
89 };
90};
91
93{
94 using G1 = ECCOpQueueTest::G1;
96
97 ECCOpQueue op_queue;
99 op_queue.empty_row_for_testing();
100 // Set hiding op for ECCVM ZK (required before get_eccvm_ops())
102 op_queue.merge();
103 const auto& eccvm_ops = op_queue.get_eccvm_ops();
104 // Index 0 is the hiding op (prepended for ECCVM ZK), so actual ops start at index 1
105 EXPECT_EQ(eccvm_ops[1].base_point, G1::one());
106 EXPECT_EQ(eccvm_ops[2].op_code.add, false);
107}
108
109TEST(ECCOpQueueTest, InternalAccumulatorCorrectness)
110{
111 using G1 = ECCOpQueueTest::G1;
112 using Fr = ECCOpQueueTest::Fr;
113
114 // Compute a simple point accumulation natively
115 auto P1 = G1::random_element();
116 auto P2 = G1::random_element();
117 auto z = Fr::random_element();
118 auto P_expected = P1 + P2 * z;
119
120 // Add the same operations to the ECC op queue; the native computation is performed under the hood.
121 ECCOpQueue op_queue;
122 op_queue.add_accumulate(P1);
123 op_queue.mul_accumulate(P2, z);
124
125 // The correct result should now be stored in the accumulator within the op queue
126 EXPECT_EQ(op_queue.get_accumulator(), P_expected);
127
128 // Adding an equality op should reset the accumulator to zero (the point at infinity)
129 op_queue.eq_and_reset();
130 EXPECT_TRUE(op_queue.get_accumulator().is_point_at_infinity());
131}
132
133// Check that the ECC op queue correctly constructs the table column polynomials for the full table, the previous table,
134// and the current subtable via successive prepending of subtables
135TEST(ECCOpQueueTest, ColumnPolynomialConstructionPrependOnly)
136{
138
139 // Instantiate an EccOpQueue and populate it with several subtables of ECC ops
140 auto op_queue = std::make_shared<bb::ECCOpQueue>();
141
142 // Check that the table polynomials have the correct form after each subtable concatenation
143 const size_t NUM_SUBTABLES = 5;
144 for (size_t i = 0; i < NUM_SUBTABLES; ++i) {
145 op_queue->initialize_new_subtable();
146 // For prepend: the last subtable becomes the first in the final table.
147 // Add hiding op at the START of the last subtable so it lands at index 0.
148 if (i == NUM_SUBTABLES - 1) {
149 op_queue->append_hiding_op(Fq::random_element(), Fq::random_element());
150 }
151 ECCOpQueueTest::populate_an_arbitrary_subtable_of_ops(op_queue, /*initialize=*/false);
152 MergeSettings settings = MergeSettings::PREPEND;
153 op_queue->merge(settings);
155 }
156
158}
159
160TEST(ECCOpQueueTest, ColumnPolynomialConstructionPrependThenAppend)
161{
163
164 // Instantiate an EccOpQueue and populate it with several subtables of ECC ops
165 auto op_queue = std::make_shared<bb::ECCOpQueue>();
166
167 // Check that the table polynomials have the correct form after each subtable concatenation
168 const size_t NUM_SUBTABLES = 2;
169 for (size_t i = 0; i < NUM_SUBTABLES; ++i) {
170 op_queue->initialize_new_subtable();
171 // For prepend: the last prepended subtable (i=1) becomes first in the final table.
172 // Add hiding op at the START of that subtable so it lands at index 0.
173 if (i == NUM_SUBTABLES - 1) {
174 op_queue->append_hiding_op(Fq::random_element(), Fq::random_element());
175 }
176 ECCOpQueueTest::populate_an_arbitrary_subtable_of_ops(op_queue, /*initialize=*/false);
177 MergeSettings settings = MergeSettings::PREPEND;
178 op_queue->merge(settings);
180 }
181
182 // Do a single append operation (goes at end, after prepended subtables)
184 MergeSettings settings = MergeSettings::APPEND;
185 op_queue->merge(settings);
187
189}
190
191TEST(ECCOpQueueTest, ColumnPolynomialConstructionPrependThenAppendAtFixedOffset)
192{
194
195 // Instantiate an EccOpQueue and populate it with several subtables of ECC ops
196 auto op_queue = std::make_shared<bb::ECCOpQueue>();
197
198 // Check that the table polynomials have the correct form after each subtable concatenation
199 const size_t NUM_SUBTABLES = 2;
200 for (size_t i = 0; i < NUM_SUBTABLES; ++i) {
201 op_queue->initialize_new_subtable();
202 // For prepend: the last prepended subtable (i=1) becomes first in the final table.
203 // Add hiding op at the START of that subtable so it lands at index 0.
204 if (i == NUM_SUBTABLES - 1) {
205 op_queue->append_hiding_op(Fq::random_element(), Fq::random_element());
206 }
207 ECCOpQueueTest::populate_an_arbitrary_subtable_of_ops(op_queue, /*initialize=*/false);
208 MergeSettings settings = MergeSettings::PREPEND;
209 op_queue->merge(settings);
211 }
212
213 // Do a single append operation at a fixed offset (sufficiently large as to not overlap with the existing table)
214 const size_t ultra_fixed_offset = op_queue->get_ultra_ops_table_num_rows() + 20;
216 MergeSettings settings = MergeSettings::APPEND;
217 op_queue->merge(settings, ultra_fixed_offset);
218 ECCOpQueueTest::check_table_column_polynomials(op_queue, settings, ultra_fixed_offset);
219
221}
222
223// Verify correct handling of point at infinity in add and mul operations
224TEST(ECCOpQueueTest, PointAtInfinityHandling)
225{
226 using G1 = ECCOpQueueTest::G1;
227 using Fr = ECCOpQueueTest::Fr;
228
229 // Test add_accumulate with point at infinity
230 {
231 ECCOpQueue op_queue;
232 auto P1 = G1::random_element();
233 G1 identity;
234 identity.self_set_infinity();
235
236 op_queue.add_accumulate(P1);
237 op_queue.add_accumulate(identity); // Adding identity should not change accumulator
238
239 EXPECT_EQ(op_queue.get_accumulator(), P1);
240 }
241
242 // Test mul_accumulate with point at infinity
243 {
244 ECCOpQueue op_queue;
245 auto P1 = G1::random_element();
246 G1 identity;
247 identity.self_set_infinity();
248 auto scalar = Fr::random_element();
249
250 op_queue.add_accumulate(P1);
251 op_queue.mul_accumulate(identity, scalar); // identity * scalar = identity, adding gives P1
252
253 EXPECT_EQ(op_queue.get_accumulator(), P1);
254 }
255
256 // Test that accumulator starts at identity element and operations work correctly
257 {
258 ECCOpQueue op_queue;
259 auto P1 = G1::random_element();
260
261 // Initial accumulator should be point at infinity
262 EXPECT_TRUE(op_queue.get_accumulator().is_point_at_infinity());
263
264 // Adding P1 to neutral element should give P1
265 op_queue.add_accumulate(P1);
266 EXPECT_EQ(op_queue.get_accumulator(), P1);
267 }
268
269 // Test mul with scalar = 0 (result should be identity)
270 {
271 ECCOpQueue op_queue;
272 auto P1 = G1::random_element();
273 auto P2 = G1::random_element();
274
275 op_queue.add_accumulate(P1);
276 op_queue.mul_accumulate(P2, Fr(0)); // 0 * P2 = infinity
277
278 // Accumulator should still be P1 (P1 + identity = P1)
279 EXPECT_EQ(op_queue.get_accumulator(), P1);
280 }
281}
282
283// Verify that the `append_hiding_op` results in hiding op in the expected positions in both ECCVM and Ultra tables.
284TEST(ECCOpQueueTest, HidingOpPositionConsistency)
285{
286 using G1 = ECCOpQueueTest::G1;
287 using Fr = ECCOpQueueTest::Fr;
289
290 auto op_queue = std::make_shared<bb::ECCOpQueue>();
291
292 // Add some regular operations
293 auto P1 = G1::random_element();
294 auto P2 = G1::random_element();
295 auto z = Fr::random_element();
296
297 op_queue->add_accumulate(P1);
298 op_queue->mul_accumulate(P2, z);
299
300 // Add hiding op with known random values
301 Fq hiding_x = Fq::random_element();
302 Fq hiding_y = Fq::random_element();
303 op_queue->append_hiding_op(hiding_x, hiding_y);
304
305 op_queue->eq_and_reset();
306 op_queue->merge();
307
308 // Get the reconstructed tables
309 const auto& eccvm_ops = op_queue->get_eccvm_ops();
310 const auto& ultra_ops = op_queue->get_ultra_ops();
311
312 // === ECCVM Table Checks ===
313 // Hiding op should be at index 0 (prepended during get_eccvm_ops())
314 const auto& eccvm_hiding_op = eccvm_ops[0];
315 EXPECT_TRUE(eccvm_hiding_op.op_code.eq);
316 EXPECT_TRUE(eccvm_hiding_op.op_code.reset);
317 EXPECT_EQ(eccvm_hiding_op.base_point.x, hiding_x);
318 EXPECT_EQ(eccvm_hiding_op.base_point.y, hiding_y);
319
320 // === Ultra Table Checks ===
321 // Without tail kernel padding, the hiding op should be at index 2:
322 // index 0: add_accumulate(P1)
323 // index 1: mul_accumulate(P2, z)
324 // index 2: append_hiding_op (eq+reset opcode)
325 // index 3: eq_and_reset
326 constexpr size_t EXPECTED_HIDING_OP_ULTRA_IDX = 2;
327 ASSERT_GT(ultra_ops.size(), EXPECTED_HIDING_OP_ULTRA_IDX);
328
329 const auto& ultra_hiding_op = ultra_ops[EXPECTED_HIDING_OP_ULTRA_IDX];
330 EXPECT_TRUE(ultra_hiding_op.op_code.eq) << "Hiding op at index 2 should have eq=true";
331 EXPECT_TRUE(ultra_hiding_op.op_code.reset) << "Hiding op at index 2 should have reset=true";
332 const size_t CHUNK_SIZE = 2 * stdlib::NUM_LIMB_BITS_IN_FIELD_SIMULATION;
333 uint256_t ultra_x = uint256_t(ultra_hiding_op.x_lo) + (uint256_t(ultra_hiding_op.x_hi) << CHUNK_SIZE);
334 uint256_t ultra_y = uint256_t(ultra_hiding_op.y_lo) + (uint256_t(ultra_hiding_op.y_hi) << CHUNK_SIZE);
335
336 EXPECT_EQ(Fq(ultra_x), eccvm_hiding_op.base_point.x);
337 EXPECT_EQ(Fq(ultra_y), eccvm_hiding_op.base_point.y);
338
339 // Verify opcodes match
340 EXPECT_EQ(ultra_hiding_op.op_code.eq, eccvm_hiding_op.op_code.eq);
341 EXPECT_EQ(ultra_hiding_op.op_code.reset, eccvm_hiding_op.op_code.reset);
342}
static void populate_an_arbitrary_subtable_of_ops(const std::shared_ptr< bb::ECCOpQueue > &op_queue, bool initialize=true)
static void check_table_column_polynomials(const std::shared_ptr< bb::ECCOpQueue > &op_queue, MergeSettings settings, std::optional< size_t > ultra_fixed_offset=std::nullopt)
Check that the table column polynomials reconstructed by the op queue have the correct relationship.
static void check_opcode_consistency_with_eccvm(const std::shared_ptr< bb::ECCOpQueue > &op_queue)
Check that the opcode values are consistent between the ultra ops table and the eccvm ops table.
Curve::AffineElement G1
Curve::ScalarField Fr
Manages ECC operations for the Goblin proving system.
UltraOp add_accumulate(const Point &to_add)
Write point addition op to queue and natively perform addition.
Point get_accumulator()
void merge(MergeSettings settings=MergeSettings::PREPEND, std::optional< size_t > ultra_fixed_offset=std::nullopt)
UltraOp append_hiding_op(const Fq &Px, const Fq &Py)
Add a hiding op with random Px, Py values to both ECCVM and Ultra ops tables.
std::vector< ECCVMOperation > & get_eccvm_ops()
UltraOp mul_accumulate(const Point &to_mul, const Fr &scalar)
Write multiply and add op to queue and natively perform operation.
UltraOp eq_and_reset()
Write equality op using internal accumulator point.
void empty_row_for_testing()
Write empty row to queue.
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
static constexpr size_t NUM_ROWS_PER_OP
bb::fq BaseField
Definition bn254.hpp:19
typename Group::affine_element AffineElement
Definition bn254.hpp:22
bb::fr ScalarField
Definition bn254.hpp:18
static constexpr affine_element affine_one
Definition group.hpp:48
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
MergeSettings
The MergeSettings define whether an current subtable will be added at the beginning (PREPEND) or at t...
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Curve::ScalarField Fr
Curve::AffineElement G1
BB_INLINE constexpr field pow(const uint256_t &exponent) const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept
curve::BN254::BaseField Fq