Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ecc_trace.cpp
Go to the documentation of this file.
2
3#include <cassert>
4#include <memory>
5
10
11namespace bb::avm2::tracegen {
12
13namespace {
14
29FF compute_lambda(bool double_predicate,
30 bool add_predicate,
31 bool result_is_infinity,
32 const EmbeddedCurvePoint& p,
33 const EmbeddedCurvePoint& q)
34{
35 // If result_is_infinity && double_predicate, then we are doubling infinity (represented as (0, 0))
36 // and must set lambda as zero, otherwise we'd be inverting zero here.
37 if (!result_is_infinity && double_predicate) {
38 return (p.x() * p.x() * 3) / (p.y() * 2);
39 }
40 if (add_predicate) {
41 return (q.y() - p.y()) / (q.x() - p.x());
42 }
43 return 0;
44}
45
51FF compute_curve_eqn_diff(const EmbeddedCurvePoint& p)
52{
53 if (p.on_curve()) {
54 return FF::zero();
55 }
56 // The curve equation is y^2 = x^3 - 17
57 const FF y2 = p.y() * p.y();
58 const FF x3 = p.x() * p.x() * p.x();
59 return y2 - (x3 - FF(17));
60}
61
62} // namespace
63
72 TraceContainer& trace)
73{
74 using C = Column;
75
76 uint32_t row = 0;
77 for (const auto& event : events) {
78 // All points in an EccAddEvent are assumed to be on the curve and normalized.
79 EmbeddedCurvePoint p = event.p;
80 EmbeddedCurvePoint q = event.q;
81 EmbeddedCurvePoint result = event.result;
82
83 bool x_match = p.x() == q.x();
84 bool y_match = p.y() == q.y();
85
86 // Choose operation:
87
88 // If both points are the same, double (double_op == 1).
89 bool double_predicate = (x_match && y_match);
90 // If both points differ and are NOT inverses, add (add_op == 1). This predicate is true when x-coordinates
91 // differ (regardless of y-coordinates). PIL constraint: sel = double_op + add_op + INVERSE_PRED, where
92 // INVERSE_PRED = x_match * (1 - y_match). When x_match=0: double_op=0, INVERSE_PRED=0, so add_op must be 1.
93 bool add_predicate = !x_match;
94 // If the points are inverses, the result is the infinity point when adding (INVERSE_PRED == 1).
95 // This is implied when x-coordinates match but the y's don't.
96 bool inverse_predicate = (x_match && !y_match);
97
98 // Assign intermediate columns:
99
100 // If our computation does not involve the point at infinity, use_computed_result == 1.
101 bool use_computed_result = !inverse_predicate && (!p.is_infinity() && !q.is_infinity());
102 // The result is the infinity point if:
103 // (1) we hit the inverse predicate, p = -q (and neither p nor q are the infinity point)*
104 // (2) or both p and q are the infinity point, p = q = O
105 // * Note: Technically, if p = q = point at infinity then p and q /are/ inverses (since p + q = p + -p =
106 // infinity), but we consider that case separately. This is because, being a SW curve, the infinity point does
107 // not have real coordinates (we represent it as (0,0)) and we treat it with edge cases.
108 bool result_is_infinity = inverse_predicate && (!p.is_infinity() && !q.is_infinity());
109 result_is_infinity = result_is_infinity || (p.is_infinity() && q.is_infinity());
110 // Check corresponding to the #[INFINITY_RESULT] relation.
111 BB_ASSERT_EQ(result_is_infinity, result.is_infinity(), "Inconsistent infinity result assumption");
112
113 // Compute lambda:
114 // For cases without infinity (use_computed_result == true) we have:
115 // result.x := lambda^2 - p.x - q.x (= COMPUTED_R_X)
116 // result.y := lambda * (p.x - result.x) - p.y (= COMPUTED_R_Y)
117 // where lambda is the 'slope' between p & q.
118 FF lambda = compute_lambda(double_predicate, add_predicate, result_is_infinity, p, q);
119
120 trace.set(row,
121 { {
122 { C::ecc_sel, 1 },
123 // Point P
124 { C::ecc_p_x, p.x() },
125 { C::ecc_p_y, p.y() },
126 { C::ecc_p_is_inf, p.is_infinity() ? 1 : 0 },
127 // Point Q
128 { C::ecc_q_x, q.x() },
129 { C::ecc_q_y, q.y() },
130 { C::ecc_q_is_inf, q.is_infinity() ? 1 : 0 },
131 // Resulting point
132 { C::ecc_r_x, result.x() },
133 { C::ecc_r_y, result.y() },
134 { C::ecc_r_is_inf, result.is_infinity() ? 1 : 0 },
135
136 // Temporary result boolean to decrease relation degree
137 { C::ecc_use_computed_result, use_computed_result },
138
139 // Check coordinates to detect edge cases (double, add and infinity)
140 { C::ecc_x_match, x_match },
141 { C::ecc_inv_x_diff, q.x() - p.x() }, // Will be inverted in batch later
142 { C::ecc_y_match, y_match },
143 { C::ecc_inv_y_diff, q.y() - p.y() }, // Will be inverted in batch later
144
145 // Witness for doubling operation
146 { C::ecc_double_op, double_predicate },
147 { C::ecc_inv_2_p_y,
148 !result_is_infinity && double_predicate ? (p.y() * 2)
149 : FF::zero() }, // Will be inverted in batch later
150
151 // Witness for add operation
152 { C::ecc_add_op, add_predicate },
153 // This is a witness for the result(r) being the point at infinity
154 // It is used to constrain that ecc_r_is_inf is correctly set.
155 { C::ecc_result_infinity, result_is_infinity },
156 // The computed 'slope' between points P and Q.
157 { C::ecc_lambda, lambda },
158 } });
159
160 row++;
161 }
162
163 // This subtrace requires 3 inverses:
164 // (1): For a standard equality check on the x coordinates (to assign x_match) /and/ as part of the lambda
165 // calculation when adding (denom = q.x - p.x)
166 // (2): For a standard equality check on the y coordinates (to assign y_match)
167 // (3): As part of the lambda calculation when doubling (denom = 2y)
168 // Batch invert the columns.
169 trace.invert_columns({ { C::ecc_inv_x_diff, C::ecc_inv_y_diff, C::ecc_inv_2_p_y } });
170}
171
181{
182 using C = Column;
183
184 // Each event corresponds to one scalar mul (s*P = R), and each event.intermediate_state corresponds to
185 // a row in the trace, which is a single iteration of the double and add algorithm (see scalar_mul.pil).
186 // This subtrace constrains the doubles/adds with the ecc subtrace (see process_add) via separate add events.
187
188 // The computation has been completed in simulation, so here we simply assign the columns. The majority
189 // of the work is to arrange the rows in reverse bit order.
190
191 uint32_t row = 1; // We start from row 1 because this trace contains shifted columns.
192 for (const auto& event : events) {
193 // Note: the below should always be 254 (= FF bit size).
194 size_t num_intermediate_states = event.intermediate_states.size();
195 // The input point is assumed to be on the curve.
196 EmbeddedCurvePoint point = event.point;
197
198 for (size_t i = 0; i < num_intermediate_states; ++i) {
199 // This trace uses reverse aggregation, so we need to process the bits in reverse.
200 size_t intermediate_state_idx = num_intermediate_states - i - 1;
201 simulation::ScalarMulIntermediateState state = event.intermediate_states[intermediate_state_idx];
202
203 // Hence, the first bit processed is the end of the trace for the event...
204 bool is_end = intermediate_state_idx == 0;
205 // ...and the final bit processed is the start of the trace:
206 bool is_start = i == 0;
207 if (is_start) {
208 BB_ASSERT_EQ(state.res, event.result, "Inconsistent result assumption");
209 }
210
211 EmbeddedCurvePoint res = state.res;
212 EmbeddedCurvePoint temp = state.temp;
213 bool bit = state.bit;
214
215 trace.set(row,
216 { { { C::scalar_mul_sel, 1 },
217 // Static columns:
218 // Scalar
219 { C::scalar_mul_scalar, event.scalar },
220 // Point P
221 { C::scalar_mul_point_x, point.x() },
222 { C::scalar_mul_point_y, point.y() },
223 { C::scalar_mul_point_inf, point.is_infinity() ? 1 : 0 },
224 // Constant (required for #[TO_RADIX] lookup)
225 { C::scalar_mul_const_two, 2 },
226 // Non static columns:
227 // Point res
228 { C::scalar_mul_res_x, res.x() },
229 { C::scalar_mul_res_y, res.y() },
230 { C::scalar_mul_res_inf, res.is_infinity() ? 1 : 0 },
231 // Flags
232 { C::scalar_mul_start, is_start },
233 { C::scalar_mul_end, is_end },
234 { C::scalar_mul_sel_not_end, !is_end },
235 // Current bit and its index
236 { C::scalar_mul_bit, bit },
237 { C::scalar_mul_bit_idx, intermediate_state_idx },
238 // Point temp
239 { C::scalar_mul_temp_x, temp.x() },
240 { C::scalar_mul_temp_y, temp.y() },
241 { C::scalar_mul_temp_inf, temp.is_infinity() ? 1 : 0 },
242 // Should add flag
243 {
244 C::scalar_mul_should_add,
245 (!is_end) && bit,
246 } } });
247
248 row++;
249 }
250 }
251}
252
262{
263 using C = Column;
264
265 uint32_t row = 0;
266
267 // Each event corresponds to one memory aware add operation (P + Q = R). Using a single row, it constrains the
268 // memory writes of the result point R and handles errors:
269 // 1) Write out of bounds (dst_out_of_range_err)
270 // 2) Point P not on the curve (p_not_on_curve_err)
271 // 3) Point Q not on the curve (q_not_on_curve_err)
272
273 // If there is no error, the trace constrains the correctness of the add result R with the ecc subtrace (see
274 // process_add) via separate add events and the memory reads of the input points with the execution trace's
275 // #[DISPATCH_TO_ECC_ADD]. The writes are handled in the trace with a permutation to memory (see #[WRITE_MEM_i]
276 // and interactions perm_ecc_mem_write_mem_i for i = 0, 1, 2).
277
278 // If there is an error, the event has an empty result point (0, 0, false), the add/write lookups/permutations are
279 // skipped, and the error flag is set to 1. This flag is checked against sel_opcode_error in #[DISPATCH_TO_ECC_ADD].
280 for (const auto& event : events) {
281 // Address cast to uint64_t to capture possible overflow.
282 uint64_t dst_addr = static_cast<uint64_t>(event.dst_address);
283
284 // Error handling, check if the destination address is out of range.
285 // The max write address is dst_addr + 2, since we write 3 values for R (x, y, is_inf).
286 bool dst_out_of_range_err = dst_addr + 2 > AVM_HIGHEST_MEM_ADDRESS;
287
288 // Error handling, check if the points are on the curve.
289 // We do not use batch inversions as we do not need to invert in the happy path.
290 bool p_is_on_curve = event.p.on_curve();
291 FF p_is_on_curve_eqn = compute_curve_eqn_diff(event.p);
292 FF p_is_on_curve_eqn_inv = p_is_on_curve ? FF::zero() : p_is_on_curve_eqn.invert();
293
294 bool q_is_on_curve = event.q.on_curve();
295 FF q_is_on_curve_eqn = compute_curve_eqn_diff(event.q);
296 FF q_is_on_curve_eqn_inv = q_is_on_curve ? FF::zero() : q_is_on_curve_eqn.invert();
297
298 bool error = dst_out_of_range_err || !p_is_on_curve || !q_is_on_curve;
299
300 // Normalized points, ensures that input infinity points are represented by (0, 0) in the ecc subtrace.
301 EmbeddedCurvePoint p_n = event.p.is_infinity() ? EmbeddedCurvePoint::infinity() : event.p;
302 EmbeddedCurvePoint q_n = event.q.is_infinity() ? EmbeddedCurvePoint::infinity() : event.q;
303
304 trace.set(row,
305 { {
306 { C::ecc_add_mem_sel, 1 },
307 { C::ecc_add_mem_execution_clk, event.execution_clk },
308 { C::ecc_add_mem_space_id, event.space_id },
309 // Error handling - dst out of range
310 { C::ecc_add_mem_max_mem_addr, AVM_HIGHEST_MEM_ADDRESS },
311 { C::ecc_add_mem_sel_dst_out_of_range_err, dst_out_of_range_err ? 1 : 0 },
312 // Error handling - p is not on curve
313 { C::ecc_add_mem_sel_p_not_on_curve_err, !p_is_on_curve ? 1 : 0 },
314 { C::ecc_add_mem_p_is_on_curve_eqn, p_is_on_curve_eqn },
315 { C::ecc_add_mem_p_is_on_curve_eqn_inv, p_is_on_curve_eqn_inv },
316 // Error handling - q is not on curve
317 { C::ecc_add_mem_sel_q_not_on_curve_err, !q_is_on_curve ? 1 : 0 },
318 { C::ecc_add_mem_q_is_on_curve_eqn, q_is_on_curve_eqn },
319 { C::ecc_add_mem_q_is_on_curve_eqn_inv, q_is_on_curve_eqn_inv },
320 // Consolidated error
321 { C::ecc_add_mem_err, error ? 1 : 0 },
322 // Memory Writes
323 { C::ecc_add_mem_dst_addr_0_, dst_addr },
324 { C::ecc_add_mem_dst_addr_1_, dst_addr + 1 },
325 { C::ecc_add_mem_dst_addr_2_, dst_addr + 2 },
326 // Input - Point P
327 { C::ecc_add_mem_p_x, event.p.x() },
328 { C::ecc_add_mem_p_y, event.p.y() },
329 { C::ecc_add_mem_p_is_inf, event.p.is_infinity() ? 1 : 0 },
330 // Input - Point Q
331 { C::ecc_add_mem_q_x, event.q.x() },
332 { C::ecc_add_mem_q_y, event.q.y() },
333 { C::ecc_add_mem_q_is_inf, event.q.is_infinity() ? 1 : 0 },
334 // Normalized input - Point P
335 { C::ecc_add_mem_p_x_n, p_n.x() },
336 { C::ecc_add_mem_p_y_n, p_n.y() },
337 // Normalized input - Point Q
338 { C::ecc_add_mem_q_x_n, q_n.x() },
339 { C::ecc_add_mem_q_y_n, q_n.y() },
340 // Output
341 { C::ecc_add_mem_sel_should_exec, error ? 0 : 1 },
342 { C::ecc_add_mem_res_x, event.result.x() },
343 { C::ecc_add_mem_res_y, event.result.y() },
344 { C::ecc_add_mem_res_is_inf, event.result.is_infinity() ? 1 : 0 },
345 } });
346
347 row++;
348 }
349}
350
353 // Scalar Mul Interactions
355 .add<lookup_scalar_mul_add_settings, InteractionType::LookupGeneric>()
357 // Memory Aware Interactions
358 // Comparison
359 .add<lookup_ecc_mem_check_dst_addr_in_range_settings, InteractionType::LookupGeneric>(Column::gt_sel)
360 // Lookup into ECC Add Subtrace
362
363} // namespace bb::avm2::tracegen
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define AVM_HIGHEST_MEM_ADDRESS
constexpr bool is_infinity() const noexcept
constexpr const BaseField & x() const noexcept
constexpr const BaseField & y() const noexcept
void process_add_with_memory(const simulation::EventEmitterInterface< simulation::EccAddMemoryEvent >::Container &events, TraceContainer &trace)
Process the ECC add memory events and populate the relevant columns in the trace. Corresponds to the ...
void process_add(const simulation::EventEmitterInterface< simulation::EccAddEvent >::Container &events, TraceContainer &trace)
Process the ECC add events and populate the relevant columns in the trace. Corresponds to the non-mem...
Definition ecc_trace.cpp:71
void process_scalar_mul(const simulation::EventEmitterInterface< simulation::ScalarMulEvent >::Container &events, TraceContainer &trace)
Process the ECC scalar multiplication events and populate the relevant columns in the trace....
static const InteractionDefinition interactions
Definition ecc_trace.hpp:22
InteractionDefinition & add(auto &&... args)
uint32_t dst_addr
TestTraceContainer trace
lookup_settings< lookup_ecc_mem_input_output_ecc_add_settings_ > lookup_ecc_mem_input_output_ecc_add_settings
lookup_settings< lookup_scalar_mul_double_settings_ > lookup_scalar_mul_double_settings
AvmFlavorSettings::FF FF
Definition field.hpp:10
StandardAffinePoint< AvmFlavorSettings::EmbeddedCurve::AffineElement > EmbeddedCurvePoint
Definition field.hpp:12
lookup_settings< lookup_scalar_mul_to_radix_settings_ > lookup_scalar_mul_to_radix_settings
simulation::PublicDataTreeReadWriteEvent event