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
135 // Temporary result boolean to decrease relation degree
136 { C::ecc_use_computed_result, use_computed_result },
137
138 // Check coordinates to detect edge cases (double, add and infinity)
139 { C::ecc_x_match, x_match },
140 { C::ecc_inv_x_diff, q.x() - p.x() }, // Will be inverted in batch later
141 { C::ecc_y_match, y_match },
142 { C::ecc_inv_y_diff, q.y() - p.y() }, // Will be inverted in batch later
143
144 // Witness for doubling operation
145 { C::ecc_double_op, double_predicate },
146 { C::ecc_inv_2_p_y,
147 !result_is_infinity && double_predicate ? (p.y() * 2)
148 : FF::zero() }, // Will be inverted in batch later
149
150 // Witness for add operation
151 { C::ecc_add_op, add_predicate },
152 // This is a witness for the result (r) being the point at infinity.
153 { C::ecc_result_infinity, result_is_infinity },
154 // The computed 'slope' between points P and Q.
155 { C::ecc_lambda, lambda },
156 } });
157
158 row++;
159 }
160
161 // This subtrace requires 3 inverses:
162 // (1): For a standard equality check on the x coordinates (to assign x_match) /and/ as part of the lambda
163 // calculation when adding (denom = q.x - p.x)
164 // (2): For a standard equality check on the y coordinates (to assign y_match)
165 // (3): As part of the lambda calculation when doubling (denom = 2y)
166 // Batch invert the columns.
167 trace.invert_columns({ { C::ecc_inv_x_diff, C::ecc_inv_y_diff, C::ecc_inv_2_p_y } });
168}
169
179{
180 using C = Column;
181
182 // Each event corresponds to one scalar mul (s*P = R), and each event.intermediate_state corresponds to
183 // a row in the trace, which is a single iteration of the double and add algorithm (see scalar_mul.pil).
184 // This subtrace constrains the doubles/adds with the ecc subtrace (see process_add) via separate add events.
185
186 // The computation has been completed in simulation, so here we simply assign the columns. The majority
187 // of the work is to arrange the rows in reverse bit order.
188
189 uint32_t row = 1; // We start from row 1 because this trace contains shifted columns.
190 for (const auto& event : events) {
191 // Note: the below should always be 254 (= FF bit size).
192 size_t num_intermediate_states = event.intermediate_states.size();
193 // The input point is assumed to be on the curve.
194 EmbeddedCurvePoint point = event.point;
195
196 for (size_t i = 0; i < num_intermediate_states; ++i) {
197 // This trace uses reverse aggregation, so we need to process the bits in reverse.
198 size_t intermediate_state_idx = num_intermediate_states - i - 1;
199 simulation::ScalarMulIntermediateState state = event.intermediate_states[intermediate_state_idx];
200
201 // Hence, the first bit processed is the end of the trace for the event...
202 bool is_end = intermediate_state_idx == 0;
203 // ...and the final bit processed is the start of the trace:
204 bool is_start = i == 0;
205 if (is_start) {
206 BB_ASSERT_EQ(state.res, event.result, "Inconsistent result assumption");
207 }
208
209 EmbeddedCurvePoint res = state.res;
210 EmbeddedCurvePoint temp = state.temp;
211 bool bit = state.bit;
212
213 trace.set(row,
214 { { { C::scalar_mul_sel, 1 },
215 // Static columns:
216 // Scalar
217 { C::scalar_mul_scalar, event.scalar },
218 // Point P
219 { C::scalar_mul_point_x, point.x() },
220 { C::scalar_mul_point_y, point.y() },
221 { C::scalar_mul_point_inf, point.is_infinity() ? 1 : 0 },
222 // Constant (required for #[TO_RADIX] lookup)
223 { C::scalar_mul_const_two, 2 },
224 // Non static columns:
225 // Point res
226 { C::scalar_mul_res_x, res.x() },
227 { C::scalar_mul_res_y, res.y() },
228 { C::scalar_mul_res_inf, res.is_infinity() ? 1 : 0 },
229 // Flags
230 { C::scalar_mul_start, is_start },
231 { C::scalar_mul_end, is_end },
232 { C::scalar_mul_sel_not_end, !is_end },
233 // Current bit and its index
234 { C::scalar_mul_bit, bit },
235 { C::scalar_mul_bit_idx, intermediate_state_idx },
236 // Point temp
237 { C::scalar_mul_temp_x, temp.x() },
238 { C::scalar_mul_temp_y, temp.y() },
239 { C::scalar_mul_temp_inf, temp.is_infinity() ? 1 : 0 },
240 // Should add flag
241 {
242 C::scalar_mul_should_add,
243 (!is_end) && bit,
244 } } });
245
246 row++;
247 }
248 }
249}
250
260{
261 using C = Column;
262
263 uint32_t row = 0;
264
265 // Each event corresponds to one memory aware add operation (P + Q = R). Using a single row, it constrains the
266 // memory writes of the result point R and handles errors:
267 // 1) Write out of bounds (dst_out_of_range_err)
268 // 2) Point P not on the curve (p_not_on_curve_err)
269 // 3) Point Q not on the curve (q_not_on_curve_err)
270
271 // If there is no error, the trace constrains the correctness of the add result R with the ecc subtrace (see
272 // process_add) via separate add events and the memory reads of the input points with the execution trace's
273 // #[DISPATCH_TO_ECC_ADD]. The writes are handled in the trace with a permutation to memory (see #[WRITE_MEM_i]
274 // and interactions perm_ecc_mem_write_mem_i for i = 0, 1).
275
276 // If there is an error, the event has an empty result point (0, 0), the add/write lookups/permutations are
277 // skipped, and the error flag is set to 1. This flag is checked against sel_opcode_error in #[DISPATCH_TO_ECC_ADD].
278 for (const auto& event : events) {
279 // Address cast to uint64_t to capture possible overflow.
280 uint64_t dst_addr = static_cast<uint64_t>(event.dst_address);
281
282 // Error handling, check if the destination address is out of range.
283 // The max write address is dst_addr + 1, since we write 2 values for R (x, y).
284 bool dst_out_of_range_err = dst_addr + 1 > AVM_HIGHEST_MEM_ADDRESS;
285
286 // Error handling, check if the points are on the curve.
287 // We do not use batch inversions as we do not need to invert in the happy path.
288 bool p_is_on_curve = event.p.on_curve();
289 FF p_is_on_curve_eqn = compute_curve_eqn_diff(event.p);
290 FF p_is_on_curve_eqn_inv = p_is_on_curve ? FF::zero() : p_is_on_curve_eqn.invert();
291
292 bool q_is_on_curve = event.q.on_curve();
293 FF q_is_on_curve_eqn = compute_curve_eqn_diff(event.q);
294 FF q_is_on_curve_eqn_inv = q_is_on_curve ? FF::zero() : q_is_on_curve_eqn.invert();
295
296 bool error = dst_out_of_range_err || !p_is_on_curve || !q_is_on_curve;
297
298 trace.set(row,
299 { {
300 { C::ecc_add_mem_sel, 1 },
301 { C::ecc_add_mem_execution_clk, event.execution_clk },
302 { C::ecc_add_mem_space_id, event.space_id },
303 // Error handling - dst out of range
304 { C::ecc_add_mem_max_mem_addr, AVM_HIGHEST_MEM_ADDRESS },
305 { C::ecc_add_mem_sel_dst_out_of_range_err, dst_out_of_range_err ? 1 : 0 },
306 // Error handling - p is not on curve
307 { C::ecc_add_mem_sel_p_not_on_curve_err, !p_is_on_curve ? 1 : 0 },
308 { C::ecc_add_mem_p_is_on_curve_eqn, p_is_on_curve_eqn },
309 { C::ecc_add_mem_p_is_on_curve_eqn_inv, p_is_on_curve_eqn_inv },
310 // Error handling - q is not on curve
311 { C::ecc_add_mem_sel_q_not_on_curve_err, !q_is_on_curve ? 1 : 0 },
312 { C::ecc_add_mem_q_is_on_curve_eqn, q_is_on_curve_eqn },
313 { C::ecc_add_mem_q_is_on_curve_eqn_inv, q_is_on_curve_eqn_inv },
314 // Consolidated error
315 { C::ecc_add_mem_err, error ? 1 : 0 },
316 // Memory Writes
317 { C::ecc_add_mem_dst_addr_0_, dst_addr },
318 { C::ecc_add_mem_dst_addr_1_, dst_addr + 1 },
319 // Input - Point P
320 { C::ecc_add_mem_p_x, event.p.x() },
321 { C::ecc_add_mem_p_y, event.p.y() },
322 // Input - Point Q
323 { C::ecc_add_mem_q_x, event.q.x() },
324 { C::ecc_add_mem_q_y, event.q.y() },
325 // Input - Infinity flags required for ECC trace
326 { C::ecc_add_mem_p_is_inf, event.p.is_infinity() ? 1 : 0 },
327 { C::ecc_add_mem_q_is_inf, event.q.is_infinity() ? 1 : 0 },
328 // Output
329 { C::ecc_add_mem_sel_should_exec, error ? 0 : 1 },
330 { C::ecc_add_mem_res_x, event.result.x() },
331 { C::ecc_add_mem_res_y, event.result.y() },
332 } });
333
334 row++;
335 }
336}
337
340 // Scalar Mul Interactions
342 .add<InteractionType::LookupGeneric, lookup_scalar_mul_add_settings>()
344 // Memory Aware Interactions
345 // Comparison
346 .add<InteractionType::LookupGeneric, lookup_ecc_mem_check_dst_addr_in_range_settings>(Column::gt_sel)
347 // Lookup into ECC Add Subtrace
349
350} // 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
AvmFlavorSettings::FF FF
Definition field.hpp:10
StandardAffinePoint< AvmFlavorSettings::EmbeddedCurve::AffineElement > EmbeddedCurvePoint
Definition field.hpp:12
simulation::PublicDataTreeReadWriteEvent event
Settings to be passed ot GenericLookupRelationImpl.