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_final_table_column_polynomials(const std::shared_ptr<bb::ECCOpQueue>& op_queue,
34 std::optional<size_t> ultra_fixed_offset = std::nullopt)
35 {
36 // Construct column polynomials corresponding to the full table (T), the table up to and including the tail
37 // (T_tail, the second to last table), and the current subtable (t_current). T and T_tail both include the ZK
38 // preamble.
39 auto table_polynomials = op_queue->construct_ultra_ops_table_columns();
40 auto tail_table_polynomials = op_queue->construct_table_columns_up_to_tail();
41 auto subtable_polynomials = op_queue->construct_current_ultra_ops_subtable_columns();
42
43 // Check T(x) = T_tail(x) + x^k * t_current(x) at a single random challenge point
44 Fr eval_challenge = Fr::random_element();
45 for (auto [table_poly, tail_table_poly, subtable_poly] :
46 zip_view(table_polynomials, tail_table_polynomials, subtable_polynomials)) {
47 const Fr table_eval = table_poly.evaluate(eval_challenge); // T(x)
48 // APPEND merge performs concatenation directly to end of previous table or at a specified fixed offset.
49 const size_t tail_table_size = op_queue->get_ultra_ops_table_num_rows_up_to_tail(); // k
50 const size_t shift_magnitude =
51 ultra_fixed_offset.has_value()
53 (ultra_fixed_offset.value() * bb::UltraEccOpsTable::NUM_ROWS_PER_OP)
54 : tail_table_size; // k
55 // T(x) = T_tail(x) + x^k * t_current(x), where k is the shift magnitude.
56 const Fr tail_table_eval = tail_table_poly.evaluate(eval_challenge); // T_tail(x)
57 const Fr shifted_subtable_eval =
58 subtable_poly.evaluate(eval_challenge) * eval_challenge.pow(shift_magnitude); // x^k * t_current(x)
59 EXPECT_EQ(table_eval, shifted_subtable_eval + tail_table_eval);
60 }
61 }
62
68 static void check_opcode_consistency_with_eccvm(const std::shared_ptr<bb::ECCOpQueue>& op_queue,
69 const bool include_zk_ops = false)
70 {
71 auto ultra_table =
72 include_zk_ops ? op_queue->get_zk_reconstructed_ultra_ops() : op_queue->get_no_zk_reconstructed_ultra_ops();
73 auto eccvm_table = op_queue->get_eccvm_ops();
74
75 size_t j = 0;
76 for (const auto& ultra_op : ultra_table) {
77 if (ultra_op.op_code.is_random_op) {
78 continue;
79 }
80 if (ultra_op.op_code.value() == 0) {
81 continue;
82 }
83 EXPECT_EQ(ultra_op.op_code.value(), eccvm_table[j++].op_code.value());
84 }
85 };
86};
87
89{
90 using G1 = ECCOpQueueTest::G1;
92
93 ECCOpQueue op_queue;
95 op_queue.empty_row_for_testing();
96 // Set hiding op for ECCVM ZK (required before get_eccvm_ops())
98 op_queue.merge();
99 const auto& eccvm_ops = op_queue.get_eccvm_ops();
100 // Index 0 is the hiding op (prepended for ECCVM ZK), so actual ops start at index 1
101 EXPECT_EQ(eccvm_ops[1].base_point, G1::one());
102 EXPECT_EQ(eccvm_ops[2].op_code.add, false);
103}
104
105TEST(ECCOpQueueTest, InternalAccumulatorCorrectness)
106{
107 using G1 = ECCOpQueueTest::G1;
108 using Fr = ECCOpQueueTest::Fr;
109
110 // Compute a simple point accumulation natively
111 auto P1 = G1::random_element();
112 auto P2 = G1::random_element();
113 auto z = Fr::random_element();
114 auto P_expected = P1 + P2 * z;
115
116 // Add the same operations to the ECC op queue; the native computation is performed under the hood.
117 ECCOpQueue op_queue;
118 op_queue.add_accumulate(P1);
119 op_queue.mul_accumulate(P2, z);
120
121 // The correct result should now be stored in the accumulator within the op queue
122 EXPECT_EQ(op_queue.get_accumulator(), P_expected);
123
124 // Adding an equality op should reset the accumulator to zero (the point at infinity)
125 op_queue.eq_and_reset();
126 EXPECT_TRUE(op_queue.get_accumulator().is_point_at_infinity());
127}
128
129// Check that the ECC op queue correctly reconstructs subtables via successive appending of subtables.
130TEST(ECCOpQueueTest, ColumnPolynomialConstruction)
131{
133
134 // Instantiate an EccOpQueue and populate it with several subtables of ECC ops
135 auto op_queue = std::make_shared<bb::ECCOpQueue>();
136
137 // Check that the table polynomials have the correct form after each subtable concatenation
138 const size_t NUM_SUBTABLES = 5;
139 for (size_t i = 0; i < NUM_SUBTABLES; ++i) {
140 op_queue->initialize_new_subtable();
141 // Add hiding op to the first subtable so the Ultra and ECCVM opcode streams have matching order.
142 if (i == 0) {
143 op_queue->append_hiding_op(Fq::random_element(), Fq::random_element());
144 }
145 ECCOpQueueTest::populate_an_arbitrary_subtable_of_ops(op_queue, /*initialize=*/false);
146 op_queue->merge();
147 }
148
149 op_queue->construct_zk_columns();
151}
152
153TEST(ECCOpQueueTest, ColumnPolynomialConstructionUpToTailWithZkThenFixedAppend)
154{
155 // Instantiate an EccOpQueue and populate it with several subtables of ECC ops
156 auto op_queue = std::make_shared<bb::ECCOpQueue>();
157
158 // Construct app/kernel subtables followed by the tail subtable.
159 const size_t NUM_SUBTABLES_THROUGH_TAIL = 3;
160 for (size_t i = 0; i < NUM_SUBTABLES_THROUGH_TAIL; ++i) {
161 op_queue->initialize_new_subtable();
162 ECCOpQueueTest::populate_an_arbitrary_subtable_of_ops(op_queue, /*initialize=*/false);
163 op_queue->merge();
164 }
165
166 op_queue->construct_zk_columns();
167
168 // Do a single append operation at a fixed offset for the hiding kernel subtable.
169 const size_t ultra_fixed_offset = op_queue->get_ultra_ops_table_num_rows() + 20;
171 op_queue->merge_fixed_append(ultra_fixed_offset);
172 auto table_up_to_tail = op_queue->construct_table_columns_up_to_tail();
173 EXPECT_EQ(table_up_to_tail[0].size(),
174 bb::UltraEccOpsTable::ZK_ULTRA_OPS + op_queue->get_ultra_ops_table_num_rows_up_to_tail());
175 ECCOpQueueTest::check_final_table_column_polynomials(op_queue, ultra_fixed_offset);
176
177 ECCOpQueueTest::check_opcode_consistency_with_eccvm(op_queue, /*include_zk_ops=*/true);
178}
179
180// Verify correct handling of point at infinity in add and mul operations
181TEST(ECCOpQueueTest, PointAtInfinityHandling)
182{
183 using G1 = ECCOpQueueTest::G1;
184 using Fr = ECCOpQueueTest::Fr;
185
186 // Test add_accumulate with point at infinity
187 {
188 ECCOpQueue op_queue;
189 auto P1 = G1::random_element();
190 G1 identity;
191 identity.self_set_infinity();
192
193 op_queue.add_accumulate(P1);
194 op_queue.add_accumulate(identity); // Adding identity should not change accumulator
195
196 EXPECT_EQ(op_queue.get_accumulator(), P1);
197 }
198
199 // Test mul_accumulate with point at infinity
200 {
201 ECCOpQueue op_queue;
202 auto P1 = G1::random_element();
203 G1 identity;
204 identity.self_set_infinity();
205 auto scalar = Fr::random_element();
206
207 op_queue.add_accumulate(P1);
208 op_queue.mul_accumulate(identity, scalar); // identity * scalar = identity, adding gives P1
209
210 EXPECT_EQ(op_queue.get_accumulator(), P1);
211 }
212
213 // Test that accumulator starts at identity element and operations work correctly
214 {
215 ECCOpQueue op_queue;
216 auto P1 = G1::random_element();
217
218 // Initial accumulator should be point at infinity
219 EXPECT_TRUE(op_queue.get_accumulator().is_point_at_infinity());
220
221 // Adding P1 to neutral element should give P1
222 op_queue.add_accumulate(P1);
223 EXPECT_EQ(op_queue.get_accumulator(), P1);
224 }
225
226 // Test mul with scalar = 0 (result should be identity)
227 {
228 ECCOpQueue op_queue;
229 auto P1 = G1::random_element();
230 auto P2 = G1::random_element();
231
232 op_queue.add_accumulate(P1);
233 op_queue.mul_accumulate(P2, Fr(0)); // 0 * P2 = infinity
234
235 // Accumulator should still be P1 (P1 + identity = P1)
236 EXPECT_EQ(op_queue.get_accumulator(), P1);
237 }
238}
239
240// Verify that the `append_hiding_op` results in hiding op in the expected positions in both ECCVM and Ultra tables.
241TEST(ECCOpQueueTest, HidingOpPositionConsistency)
242{
243 using G1 = ECCOpQueueTest::G1;
244 using Fr = ECCOpQueueTest::Fr;
246
247 auto op_queue = std::make_shared<bb::ECCOpQueue>();
248
249 // Add some regular operations
250 auto P1 = G1::random_element();
251 auto P2 = G1::random_element();
252 auto z = Fr::random_element();
253
254 op_queue->add_accumulate(P1);
255 op_queue->mul_accumulate(P2, z);
256
257 // Add hiding op with known random values
258 Fq hiding_x = Fq::random_element();
259 Fq hiding_y = Fq::random_element();
260 op_queue->append_hiding_op(hiding_x, hiding_y);
261
262 op_queue->eq_and_reset();
263 op_queue->merge();
264
265 // Get the reconstructed ECCVM table and raw Ultra table. This test is checking the explicitly appended hiding op
266 // in the raw subtable, not the Chonk ZK-prefixed reconstruction.
267 const auto& eccvm_ops = op_queue->get_eccvm_ops();
268 const auto& ultra_ops = op_queue->get_no_zk_reconstructed_ultra_ops();
269
270 // === ECCVM Table Checks ===
271 // Hiding op should be at index 0 (prepended during get_eccvm_ops())
272 const auto& eccvm_hiding_op = eccvm_ops[0];
273 EXPECT_TRUE(eccvm_hiding_op.op_code.eq);
274 EXPECT_TRUE(eccvm_hiding_op.op_code.reset);
275 EXPECT_EQ(eccvm_hiding_op.base_point.x, hiding_x);
276 EXPECT_EQ(eccvm_hiding_op.base_point.y, hiding_y);
277
278 // === Ultra Table Checks ===
279 // By construction, the hiding op should be at index 2:
280 // index 0: add_accumulate(P1)
281 // index 1: mul_accumulate(P2, z)
282 // index 2: append_hiding_op (eq+reset opcode)
283 // index 3: eq_and_reset
284 constexpr size_t EXPECTED_HIDING_OP_ULTRA_IDX = 2;
285 ASSERT_GT(ultra_ops.size(), EXPECTED_HIDING_OP_ULTRA_IDX);
286
287 const auto& ultra_hiding_op = ultra_ops[EXPECTED_HIDING_OP_ULTRA_IDX];
288 EXPECT_TRUE(ultra_hiding_op.op_code.eq) << "Hiding op at index 2 should have eq=true";
289 EXPECT_TRUE(ultra_hiding_op.op_code.reset) << "Hiding op at index 2 should have reset=true";
290 const size_t CHUNK_SIZE = 2 * stdlib::NUM_LIMB_BITS_IN_FIELD_SIMULATION;
291 uint256_t ultra_x = uint256_t(ultra_hiding_op.x_lo) + (uint256_t(ultra_hiding_op.x_hi) << CHUNK_SIZE);
292 uint256_t ultra_y = uint256_t(ultra_hiding_op.y_lo) + (uint256_t(ultra_hiding_op.y_hi) << CHUNK_SIZE);
293
294 EXPECT_EQ(Fq(ultra_x), eccvm_hiding_op.base_point.x);
295 EXPECT_EQ(Fq(ultra_y), eccvm_hiding_op.base_point.y);
296
297 // Verify opcodes match
298 EXPECT_EQ(ultra_hiding_op.op_code.eq, eccvm_hiding_op.op_code.eq);
299 EXPECT_EQ(ultra_hiding_op.op_code.reset, eccvm_hiding_op.op_code.reset);
300}
static void populate_an_arbitrary_subtable_of_ops(const std::shared_ptr< bb::ECCOpQueue > &op_queue, bool initialize=true)
static void check_opcode_consistency_with_eccvm(const std::shared_ptr< bb::ECCOpQueue > &op_queue, const bool include_zk_ops=false)
Check that the opcode values are consistent between the ultra ops table and the eccvm ops table.
static void check_final_table_column_polynomials(const std::shared_ptr< bb::ECCOpQueue > &op_queue, std::optional< size_t > ultra_fixed_offset=std::nullopt)
Check that the table column polynomials reconstructed by the op queue have the correct relationship.
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()
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
static constexpr size_t ZK_ULTRA_OPS
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:50
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
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