Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
translator_circuit_builder.test.cpp
Go to the documentation of this file.
5#include <array>
6#include <cstddef>
7#include <gtest/gtest.h>
8
9using namespace bb;
10namespace {
12
16fq compute_expected_result(const std::shared_ptr<ECCOpQueue>& op_queue,
17 const fq& batching_challenge,
18 const fq& x,
19 bool include_zk = true)
20{
21 using Fq = fq;
22 Fq x_inv = x.invert();
23 Fq op_accumulator = Fq(0);
24 Fq p_x_accumulator = Fq(0);
25 Fq p_y_accumulator = Fq(0);
26 Fq z_1_accumulator = Fq(0);
27 Fq z_2_accumulator = Fq(0);
28 Fq x_pow = Fq(1);
29
30 const auto& ultra_ops =
31 include_zk ? op_queue->get_zk_reconstructed_ultra_ops() : op_queue->get_no_zk_reconstructed_ultra_ops();
32 for (const auto& ultra_op : ultra_ops) {
33 if (ultra_op.op_code.is_random_op || ultra_op.op_code.value() == 0) {
34 continue;
35 }
36 op_accumulator = op_accumulator * x_inv + ultra_op.op_code.value();
37 const auto [x_fq, y_fq] = ultra_op.get_base_point_standard_form();
38 p_x_accumulator = p_x_accumulator * x_inv + x_fq;
39 p_y_accumulator = p_y_accumulator * x_inv + y_fq;
40 z_1_accumulator = z_1_accumulator * x_inv + uint256_t(ultra_op.z_1);
41 z_2_accumulator = z_2_accumulator * x_inv + uint256_t(ultra_op.z_2);
42 x_pow *= x;
43 }
44 x_pow *= x_inv;
45
46 // Compute batched polynomial evaluation using Horner's method
47 Fq total = z_2_accumulator; // z₂
48 total *= batching_challenge; // z₂ * v
49 total += z_1_accumulator; // z₂ * v + z₁
50 total *= batching_challenge; // z₂ * v² + z₁ * v
51 total += p_y_accumulator; // z₂ * v² + z₁ * v + P.y
52 total *= batching_challenge; // z₂ * v³ + z₁ * v² + P.y * v
53 total += p_x_accumulator; // z₂ * v³ + z₁ * v² + P.y * v + P.x
54 total *= batching_challenge; // z₂ * v⁴ + z₁ * v³ + P.y * v² + P.x * v
55 total += op_accumulator; // z₂ * v⁴ + z₁ * v³ + P.y * v² + P.x * v + op
56 total *= x_pow; // x_pow * ( ... )
57 return total;
58}
59} // namespace
61
62// Test that the circuit can handle several accumulations correctly
63TEST(TranslatorCircuitBuilder, SeveralOperationCorrectness)
64{
65 using point = g1::affine_element;
66 using scalar = fr;
67 using Fq = fq;
68
69 auto P1 = point::random_element();
70 auto P2 = point::random_element();
71 auto z = scalar::random_element();
72
73 // Add the same operations to the ECC op queue; the native computation is performed under the hood.
74 auto op_queue = std::make_shared<ECCOpQueue>();
75 op_queue->construct_zk_columns();
76 op_queue->add_accumulate(P1);
77 op_queue->mul_accumulate(P2, z);
78 op_queue->eq_and_reset();
79 op_queue->merge();
80
81 op_queue->add_accumulate(P1);
82 op_queue->mul_accumulate(P2, z);
83 op_queue->add_accumulate(P1);
84 op_queue->mul_accumulate(P2, z);
85 op_queue->eq_and_reset();
86
87 // Placeholder for randomness
88 op_queue->random_op_ultra_only();
89 op_queue->random_op_ultra_only();
90 op_queue->merge_fixed_append(op_queue->get_append_offset());
91
92 Fq batching_challenge = Fq::random_element();
94
95 // Create circuit builder and feed the queue inside
96 auto circuit_builder = TranslatorCircuitBuilder(batching_challenge, x, op_queue);
97 EXPECT_TRUE(CircuitChecker::check(circuit_builder));
98
99 // Verify the accumulator result is correct
100 Fq expected_result = compute_expected_result(op_queue, batching_challenge, x);
101 EXPECT_EQ(expected_result, CircuitChecker::get_computation_result(circuit_builder));
102}
103
104// Test with minimal operations (only required no-ops and random ops)
106{
107 using Fq = fq;
108
109 auto op_queue = std::make_shared<ECCOpQueue>();
110 op_queue->construct_zk_columns();
111 op_queue->eq_and_reset();
112 op_queue->merge();
113 op_queue->random_op_ultra_only();
114 op_queue->random_op_ultra_only();
115 op_queue->merge_fixed_append(op_queue->get_append_offset());
116
117 Fq batching_challenge = Fq::random_element();
119
120 auto circuit_builder = TranslatorCircuitBuilder(batching_challenge, x, op_queue);
121 EXPECT_TRUE(CircuitChecker::check(circuit_builder));
122}
123
124// Test with only add operations
126{
127 using point = g1::affine_element;
128 using Fq = fq;
129
130 auto P1 = point::random_element();
131 auto P2 = point::random_element();
132
133 auto op_queue = std::make_shared<ECCOpQueue>();
134 op_queue->construct_zk_columns();
135 op_queue->add_accumulate(P1);
136 op_queue->add_accumulate(P2);
137 op_queue->add_accumulate(P1);
138 op_queue->eq_and_reset();
139 op_queue->merge();
140 op_queue->random_op_ultra_only();
141 op_queue->random_op_ultra_only();
142 op_queue->merge_fixed_append(op_queue->get_append_offset());
143
144 Fq batching_challenge = Fq::random_element();
146
147 auto circuit_builder = TranslatorCircuitBuilder(batching_challenge, x, op_queue);
148 EXPECT_TRUE(CircuitChecker::check(circuit_builder));
149
150 // Verify the accumulator result is correct
151 Fq expected_result = compute_expected_result(op_queue, batching_challenge, x);
152 EXPECT_EQ(expected_result, CircuitChecker::get_computation_result(circuit_builder));
153}
154
155// Test with only multiplication operations
157{
158 using point = g1::affine_element;
159 using scalar = fr;
160 using Fq = fq;
161
162 auto P = point::random_element();
163 auto z1 = scalar::random_element();
164 auto z2 = scalar::random_element();
165
166 auto op_queue = std::make_shared<ECCOpQueue>();
167 op_queue->construct_zk_columns();
168 op_queue->mul_accumulate(P, z1);
169 op_queue->mul_accumulate(P, z2);
170 op_queue->eq_and_reset();
171 op_queue->merge();
172 op_queue->random_op_ultra_only();
173 op_queue->random_op_ultra_only();
174 op_queue->merge_fixed_append(op_queue->get_append_offset());
175
176 Fq batching_challenge = Fq::random_element();
178
179 auto circuit_builder = TranslatorCircuitBuilder(batching_challenge, x, op_queue);
180 EXPECT_TRUE(CircuitChecker::check(circuit_builder));
181
182 // Verify the accumulator result is correct
183 Fq expected_result = compute_expected_result(op_queue, batching_challenge, x);
184 EXPECT_EQ(expected_result, CircuitChecker::get_computation_result(circuit_builder));
185}
186
187// Test with multiple no-ops interspersed with real operations
189{
190 using point = g1::affine_element;
191 using Fq = fq;
192
193 auto P = point::random_element();
194
195 auto op_queue = std::make_shared<ECCOpQueue>();
196 op_queue->construct_zk_columns();
197 op_queue->add_accumulate(P);
198 op_queue->add_accumulate(P);
199 op_queue->eq_and_reset();
200 op_queue->merge();
201 op_queue->random_op_ultra_only();
202 op_queue->random_op_ultra_only();
203 op_queue->merge_fixed_append(op_queue->get_append_offset());
204
205 Fq batching_challenge = Fq::random_element();
207
208 auto circuit_builder = TranslatorCircuitBuilder(batching_challenge, x, op_queue);
209 EXPECT_TRUE(CircuitChecker::check(circuit_builder));
210
211 // Verify the accumulator result is correct
212 Fq expected_result = compute_expected_result(op_queue, batching_challenge, x);
213 EXPECT_EQ(expected_result, CircuitChecker::get_computation_result(circuit_builder));
214}
215
216// Test with point at infinity
218{
219 using point = g1::affine_element;
220 using Fq = fq;
221
222 auto P_infinity = point::infinity();
223
224 auto op_queue = std::make_shared<ECCOpQueue>();
225 op_queue->construct_zk_columns();
226 op_queue->add_accumulate(P_infinity);
227 op_queue->eq_and_reset();
228 op_queue->merge();
229 op_queue->random_op_ultra_only();
230 op_queue->random_op_ultra_only();
231 op_queue->merge_fixed_append(op_queue->get_append_offset());
232
233 Fq batching_challenge = Fq::random_element();
235
236 auto circuit_builder = TranslatorCircuitBuilder(batching_challenge, x, op_queue);
237 EXPECT_TRUE(CircuitChecker::check(circuit_builder));
238
239 // Verify the accumulator result is correct (point at infinity should contribute P.x=0, P.y=0)
240 Fq expected_result = compute_expected_result(op_queue, batching_challenge, x);
241 EXPECT_EQ(expected_result, CircuitChecker::get_computation_result(circuit_builder));
242}
243
244// Test with scalar = 0
246{
247 using point = g1::affine_element;
248 using scalar = fr;
249 using Fq = fq;
250
251 auto P = point::random_element();
252 auto zero = scalar::zero();
253
254 auto op_queue = std::make_shared<ECCOpQueue>();
255 op_queue->construct_zk_columns();
256 op_queue->mul_accumulate(P, zero);
257 op_queue->eq_and_reset();
258 op_queue->merge();
259 op_queue->random_op_ultra_only();
260 op_queue->random_op_ultra_only();
261 op_queue->merge_fixed_append(op_queue->get_append_offset());
262
263 Fq batching_challenge = Fq::random_element();
265
266 auto circuit_builder = TranslatorCircuitBuilder(batching_challenge, x, op_queue);
267 EXPECT_TRUE(CircuitChecker::check(circuit_builder));
268
269 // Verify the accumulator result is correct (z=0 should result in P.x*0, P.y*0)
270 Fq expected_result = compute_expected_result(op_queue, batching_challenge, x);
271 EXPECT_EQ(expected_result, CircuitChecker::get_computation_result(circuit_builder));
272}
273
274// Test with many operations to stress test the circuit
276{
277 using point = g1::affine_element;
278 using scalar = fr;
279 using Fq = fq;
280
281 auto op_queue = std::make_shared<ECCOpQueue>();
282 op_queue->construct_zk_columns();
283
284 // Add many operations
285 for (size_t i = 0; i < 20; ++i) {
286 auto P = point::random_element();
287 auto z = scalar::random_element();
288 op_queue->add_accumulate(P);
289 op_queue->mul_accumulate(P, z);
290 }
291
292 op_queue->eq_and_reset();
293 op_queue->merge();
294 op_queue->random_op_ultra_only();
295 op_queue->random_op_ultra_only();
296 op_queue->merge_fixed_append(op_queue->get_append_offset());
297
298 Fq batching_challenge = Fq::random_element();
300
301 auto circuit_builder = TranslatorCircuitBuilder(batching_challenge, x, op_queue);
302 EXPECT_TRUE(CircuitChecker::check(circuit_builder));
303
304 // Verify the accumulator result is correct (stress test with many operations)
305 Fq expected_result = compute_expected_result(op_queue, batching_challenge, x);
306 EXPECT_EQ(expected_result, CircuitChecker::get_computation_result(circuit_builder));
307}
308
309// Test determinism - same inputs should produce same circuit and same result
311{
312 using point = g1::affine_element;
313 using scalar = fr;
314 using Fq = fq;
315
316 auto P = point::random_element();
317 auto z = scalar::random_element();
318 Fq batching_challenge = Fq::random_element();
320
321 // Build first circuit
322 auto op_queue1 = std::make_shared<ECCOpQueue>();
323 op_queue1->construct_zk_columns();
324 op_queue1->add_accumulate(P);
325 op_queue1->mul_accumulate(P, z);
326 op_queue1->eq_and_reset();
327 op_queue1->merge();
328 op_queue1->random_op_ultra_only();
329 op_queue1->random_op_ultra_only();
330 op_queue1->merge_fixed_append(op_queue1->get_append_offset());
331
332 auto circuit_builder1 = TranslatorCircuitBuilder(batching_challenge, x, op_queue1);
333 auto result1 = CircuitChecker::get_computation_result(circuit_builder1);
334
335 // Build second circuit with same operations
336 auto op_queue2 = std::make_shared<ECCOpQueue>();
337 op_queue2->construct_zk_columns();
338 op_queue2->add_accumulate(P);
339 op_queue2->mul_accumulate(P, z);
340 op_queue2->eq_and_reset();
341 op_queue2->merge();
342 op_queue2->random_op_ultra_only();
343 op_queue2->random_op_ultra_only();
344 op_queue2->merge_fixed_append(op_queue2->get_append_offset());
345
346 auto circuit_builder2 = TranslatorCircuitBuilder(batching_challenge, x, op_queue2);
347 auto result2 = CircuitChecker::get_computation_result(circuit_builder2);
348
349 // Compute contributions
350 Fq op_queue_1_with_randomness = compute_expected_result(op_queue1, batching_challenge, x, /*include_zk=*/true);
351 Fq op_queue_2_with_randomness = compute_expected_result(op_queue2, batching_challenge, x, /*include_zk=*/true);
352 Fq op_queue_1_without_randomness = compute_expected_result(op_queue1, batching_challenge, x, /*include_zk=*/false);
353 Fq op_queue_2_without_randomness = compute_expected_result(op_queue2, batching_challenge, x, /*include_zk=*/false);
354 Fq op_queue_1_randomness =
355 op_queue_1_with_randomness - op_queue_1_without_randomness * x.invert().pow(UltraEccOpsTable::ZK_ULTRA_OPS);
356 Fq op_queue_2_randomness =
357 op_queue_2_with_randomness - op_queue_2_without_randomness * x.invert().pow(UltraEccOpsTable::ZK_ULTRA_OPS);
358
359 EXPECT_EQ(result1 - result2, op_queue_1_randomness - op_queue_2_randomness);
360}
The unified interface for check circuit functionality implemented in the specialized CircuitChecker c...
TranslatorCircuitBuilder creates a circuit that evaluates the correctness of the evaluation of EccOpQ...
static Fq get_computation_result(const Builder &circuit)
Get the result of accumulation, stored as 4 binary limbs in the first row of the circuit.
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
static constexpr size_t ZK_ULTRA_OPS
group_elements::affine_element< Fq, Fr, Params > affine_element
Definition group.hpp:44
bool expected_result
numeric::RNG & engine
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:245
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
field< Bn254FqParams > fq
Definition fq.hpp:153
field< Bn254FrParams > fr
Definition fr.hpp:155
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
BB_INLINE constexpr field pow(const uint256_t &exponent) const noexcept
constexpr field invert() const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept