Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
transcript_builder.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: not started, auditors: [], date: YYYY-MM-DD }
3// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
5// =====================
6
7#pragma once
8
12
13namespace bb {
14
16 public:
19 using Element = typename CycleGroup::element;
21 using Accumulator = typename std::vector<Element>;
22
25 // These fields are populated in the first loop
27
28 bool transcript_msm_infinity = false; // are we at the end of an MSM *and* is the output the point at infinity?
29 bool accumulator_not_empty = false; // not(is the accumulator either empty or point-at-infinity?)
30 bool q_add = false;
31 bool q_mul = false;
32 bool q_eq = false;
33 bool q_reset_accumulator = false;
34 bool msm_transition = false;
35 uint32_t pc = 0;
36 uint32_t msm_count = 0; // number of multiplications in the MSM *up until and not including* this step.
38 false; // is the number of scalar muls we have completed at the end of our "MSM block" zero?
39 FF base_x = 0; // [P] = (base_x, base_y)
40 FF base_y = 0; // [P] = (base_x, base_y)
41 bool base_infinity = false; // is [P] == neutral element?
42 uint256_t z1 = 0; // `scalar` = z1 - \lambda * z2 = z1 + \zeta * z2, where $\zeta$ is a primitive sixth root of
43 // unity and \lambda is -\zeta.
44 uint256_t z2 = 0; // `scalar` = z1 - \lambda * z2 = z1 + \zeta * z2, where $\zeta$ is a primitive sixth root of
45 // unity and \lambda is -\zeta.
46 bool z1_zero = false; // `z1 == 0`
47 bool z2_zero = false; // `z2 == 0`
48 uint32_t opcode = 0; // opcode value, in {0, .., 15}, given by 8 * q_add + 4 * q_mul + 2 * q_eq + q_reset.
49
51 // These fields are populated after converting projective to affine coordinates
53
54 // [A] is the current accumulated point, in affine coordinates, for all EC operations.
55 // this is affected by an `add` op-code, or the end of an MSM, or a reset.
56 FF accumulator_x = 0; // [A] = (`accumulator_x`, `accumulator_y`)
57 FF accumulator_y = 0; // [A] = (`accumulator_x`, `accumulator_y`)
58
59 // the following two are accumulators for the MSM.
60 // the difference between (`msm_output_x`, `msm_output_y`) and (`transcript_msm_intermediate_x`,
61 // `transcript_msm_intermediate_y`) is the OFFSET.
62 FF msm_output_x = 0; // if we are at the end of an MSM, output of MSM + OFFSET = (`msm_output_x`,
63 // `msm_output_y`), else, 0.
64 FF msm_output_y = 0; // if we are at the end of an MSM, output of MSM + OFFSET = (`msm_output_x`,
65 // `msm_output_y`), else 0.
67 0; // if we are at the end of an MSM, output of MSM =
68 // (`transcript_msm_intermediate_x`, `transcript_msm_intermediate_y`), else, 0.
70 0; // if we are at the end of an MSM, output of MSM =
71 // (`transcript_msm_intermediate_x`, `transcript_msm_intermediate_y`), else, 0.
72
74 // Computed during the lambda numerator and denominator computation
79 // Computed after the batch inversion
86 };
87
98 {
99 static constexpr auto offset_generator_base =
100 get_precomputed_generators<CycleGroup, "ECCVM_OFFSET_GENERATOR", 1>()[0];
101 static const AffineElement result =
102 AffineElement(Element(offset_generator_base) * grumpkin::fq(uint256_t(1) << 124));
103 return result;
104 }
106 {
107 return AffineElement(Element(other) - offset_generator());
108 }
109
110 // maintains the state of the VM at any given "time" (i.e., at any given value of pc).
111 struct VMState {
112 uint32_t pc = 0; // decreasing program counter that tracks the total number of multiplications that our virtual
113 // machine has left to compute.
114 uint32_t count = 0; // Number of muls in the current MSM _excluding the current row_.
115 Element accumulator = CycleGroup::affine_point_at_infinity; // accumulator for all group operations.
117 offset_generator(); // accumulator for the current MSM with an offset. (we start with offset_generator,
118 // i.e. random element of the group, to avoid bifurcated logic at each operation step.)
120 };
121
137 static std::vector<TranscriptRow> compute_rows(const std::vector<ECCVMOperation>& vm_operations,
138 const uint32_t total_number_of_muls)
139 {
140 const size_t num_vm_entries = vm_operations.size();
141 // The transcript contains an extra zero row at the beginning and the accumulated state at the end
142 const size_t transcript_size = num_vm_entries + 2;
143 // `transcript_state[i+1]` corresponds to `vm_operation[i]`.
144 std::vector<TranscriptRow> transcript_state(transcript_size);
145
146 // These vectors track quantities that we need to invert.
147 // We fill these vectors and then perform batch inversions to amortize the cost of FF inverts
148 std::vector<FF> inverse_trace_x(num_vm_entries);
149 std::vector<FF> inverse_trace_y(num_vm_entries);
150 std::vector<FF> transcript_msm_x_inverse_trace(num_vm_entries);
151 std::vector<FF> add_lambda_denominator(num_vm_entries);
152 std::vector<FF> add_lambda_numerator(num_vm_entries);
153 std::vector<FF> msm_count_at_transition_inverse_trace(num_vm_entries);
154
155 Accumulator msm_accumulator_trace(num_vm_entries); // ith entry is either neutral element or the value of the
156 // just-completed MSM (shifted by the OFFSET).
157 Accumulator accumulator_trace(num_vm_entries); // ith entry is the total accumulated value up-to-now
158 Accumulator intermediate_accumulator_trace(
159 num_vm_entries); // ith entry is either the neutral element, or the actual value of the just-completed MSM
160 // (i.e., there is no OFFSET).
161
162 VMState state{
163 .pc = total_number_of_muls,
164 .count = 0,
166 .msm_accumulator = offset_generator(),
167 .is_accumulator_empty = true,
168 };
169
170 VMState updated_state;
171
172 // add an empty row: first row is all zeroes because of our shiftable polynomials.
173 transcript_state[0] = (TranscriptRow{});
174
175 // during the first iteration over the ECCOpQueue, the operations are being performed using Jacobian (a.k.a.
176 // projective) coordinates and the base point coordinates are recorded in the transcript. at the same time, the
177 // transcript logic is being populated
178 for (size_t i = 0; i < num_vm_entries; i++) {
179 TranscriptRow& row = transcript_state[i + 1];
180 const ECCVMOperation& entry = vm_operations[i];
181 updated_state = state;
182
183 const bool is_mul = entry.op_code.mul;
184 const bool is_add = entry.op_code.add;
185 const bool z1_zero = is_mul ? entry.z1 == 0 : true;
186 const bool z2_zero = is_mul ? entry.z2 == 0 : true;
187
188 const bool base_point_infinity = entry.base_point.is_point_at_infinity();
189 uint32_t num_muls = 0; // number of 128-bit multiplications the vm processes in this op_code.
190 // `num_muls` ∈ {0, 1, 2}.
191 if (is_mul) {
192 num_muls = static_cast<uint32_t>(!z1_zero) + static_cast<uint32_t>(!z2_zero);
193 if (base_point_infinity) {
194 num_muls = 0;
195 }
196 }
197 updated_state.pc = state.pc - num_muls;
198 // if we are at a `reset` or null op, reset the state.
199 // logically, we should add `updated_state.count = 0`, but this is taken care of later by conditional
200 // logic and hence is unnecessary here.
201 if (entry.op_code.reset || entry.op_code.value() == 0) {
202 updated_state.is_accumulator_empty = true;
204 updated_state.msm_accumulator = offset_generator();
205 }
206
207 const bool last_row = (i == (num_vm_entries - 1));
208 // next_not_msm == True if either we are at the last row or the next op_code is *not* a mul.
209 const bool next_not_msm = last_row || !vm_operations[i + 1].op_code.mul;
210
211 // `msm_transition == True` iff we are at the end of an MSM.
212 // this holds iff: current op_code is `mul`, `next_not_msm == True`, and the total number of muls so far in
213 // this MSM (including this op_code) is positive. This later total number
214 // is `state.count + num_muls`.
215 const bool msm_transition = is_mul && next_not_msm && (state.count + num_muls > 0);
216
217 // determine ongoing/continuing msm and update the respective counter
218 const bool current_ongoing_msm = is_mul && !next_not_msm;
219 // we reset the count in updated state if we are not accumulating and not doing an msm
220 updated_state.count = current_ongoing_msm ? state.count + num_muls : 0;
221
222 // process state based on whether we are at a `mul`, then whether or not this is the last mul in an MSM,
223 // then finally if this is an add. note that this mutates `updated_state`. (note that the middle option
224 // depends on the first.)
225 if (is_mul) {
226 process_mul(entry, updated_state, state);
227 }
228
229 if (msm_transition) {
230 process_msm_transition(row, updated_state, state);
231 } else {
232 msm_accumulator_trace[i] = Element::infinity();
233 intermediate_accumulator_trace[i] = Element::infinity();
234 }
235
236 if (is_add) {
237 process_add(entry, updated_state, state);
238 }
239
240 // populate the first group of TranscriptRow entries
241 populate_transcript_row(row, entry, state, num_muls, msm_transition, next_not_msm);
242
243 msm_count_at_transition_inverse_trace[i] = ((state.count + num_muls) == 0) ? 0 : FF(state.count + num_muls);
244
245 // update the accumulators. note that `msm_accumulator_trace` and `intermediate_accumulate_trace` are the
246 // point-at-infinity *unless* we are at the end of an MSM.
247 accumulator_trace[i] = state.accumulator;
248 msm_accumulator_trace[i] = msm_transition ? updated_state.msm_accumulator : Element::infinity();
249 intermediate_accumulator_trace[i] =
250 msm_transition ? (updated_state.msm_accumulator - offset_generator()) : Element::infinity();
251
252 state = updated_state;
253 // if we are the last `mul`in an MSM, set the next state's `msm_accumulator` to the offset.
254 if (is_mul && next_not_msm) {
256 }
257 }
258 // compute affine coordinates of the accumulated points
259 normalize_accumulators(accumulator_trace, msm_accumulator_trace, intermediate_accumulator_trace);
260
261 // add required affine coordinates to the transcript
263 transcript_state, accumulator_trace, msm_accumulator_trace, intermediate_accumulator_trace);
264
265 // process the slopes when adding points or results of MSMs. to increase efficiency, we use batch inversion
266 // after the loop
267 for (size_t i = 0; i < accumulator_trace.size(); ++i) {
268 TranscriptRow& row = transcript_state[i + 1];
269 const bool msm_transition = row.msm_transition;
270
271 const ECCVMOperation& entry = vm_operations[i];
272 const bool is_add = entry.op_code.add;
273
274 if (msm_transition || is_add) {
275 // compute the differences between point coordinates
277 row,
278 intermediate_accumulator_trace[i],
279 transcript_msm_x_inverse_trace[i],
280 msm_accumulator_trace[i],
281 accumulator_trace[i],
282 inverse_trace_x[i],
283 inverse_trace_y[i]);
284
285 // compute the numerators and denominators of slopes between the points
287 entry,
288 intermediate_accumulator_trace[i],
289 accumulator_trace[i],
290 add_lambda_numerator[i],
291 add_lambda_denominator[i]);
292 } else {
295 add_lambda_numerator[i] = 0;
296 add_lambda_denominator[i] = 0;
297 inverse_trace_x[i] = 0;
298 inverse_trace_y[i] = 0;
299 }
300 }
301
302 // Perform all required inversions at once
303 FF::batch_invert(&inverse_trace_x[0], num_vm_entries);
304 FF::batch_invert(&inverse_trace_y[0], num_vm_entries);
305 FF::batch_invert(&transcript_msm_x_inverse_trace[0], num_vm_entries);
306 FF::batch_invert(&add_lambda_denominator[0], num_vm_entries);
307 FF::batch_invert(&msm_count_at_transition_inverse_trace[0], num_vm_entries);
308
309 // Populate the fields of the transcript row containing inverted scalars
310 for (size_t i = 0; i < num_vm_entries; ++i) {
311 TranscriptRow& row = transcript_state[i + 1];
312 row.base_x_inverse = inverse_trace_x[i];
313 row.base_y_inverse = inverse_trace_y[i];
314 row.transcript_msm_x_inverse = transcript_msm_x_inverse_trace[i];
315 row.transcript_add_lambda = add_lambda_numerator[i] * add_lambda_denominator[i];
316 row.msm_count_at_transition_inverse = msm_count_at_transition_inverse_trace[i];
317 }
318
319 // process the final row containing the result of the sequence of group ops in ECCOpQueue
320 finalize_transcript(transcript_state, updated_state);
321
322 return transcript_state;
323 }
324
325 private:
347 const ECCVMOperation& entry,
348 const VMState& state,
349 const uint32_t num_muls,
350 const bool msm_transition,
351 const bool next_not_msm)
352 {
353 const bool base_point_infinity = entry.base_point.is_point_at_infinity();
354
356 row.q_add = entry.op_code.add;
357 row.q_mul = entry.op_code.mul;
358 row.q_eq = entry.op_code.eq;
360 row.msm_transition = msm_transition;
361 row.pc = state.pc;
362 row.msm_count = state.count;
363 row.msm_count_zero_at_transition = ((state.count + num_muls) == 0) && entry.op_code.mul && next_not_msm;
364 row.base_x = ((entry.op_code.add || entry.op_code.mul || entry.op_code.eq) && !base_point_infinity)
365 ? entry.base_point.x
366 : 0;
367 row.base_y = ((entry.op_code.add || entry.op_code.mul || entry.op_code.eq) && !base_point_infinity)
368 ? entry.base_point.y
369 : 0;
370 row.base_infinity =
371 (entry.op_code.add || entry.op_code.mul || entry.op_code.eq) ? (base_point_infinity ? 1 : 0) : 0;
372 row.z1 = entry.op_code.mul ? entry.z1 : 0;
373 row.z2 = entry.op_code.mul ? entry.z2 : 0;
374 row.z1_zero = entry.z1 == 0;
375 row.z2_zero = entry.z2 == 0;
376 row.opcode = entry.op_code.value();
377 }
378
390 static void process_mul(const ECCVMOperation& entry, VMState& updated_state, const VMState& state)
391 {
392 const auto P = typename CycleGroup::element(entry.base_point);
393 const auto R = typename CycleGroup::element(state.msm_accumulator);
394 updated_state.msm_accumulator = R + P * entry.mul_scalar_full;
395 }
396
407 static void process_add(const ECCVMOperation& entry, VMState& updated_state, const VMState& state)
408 {
409
410 if (state.is_accumulator_empty) {
411 updated_state.accumulator = entry.base_point;
412 } else {
413 updated_state.accumulator = Element(state.accumulator) + entry.base_point;
414 }
415 updated_state.is_accumulator_empty = updated_state.accumulator.is_point_at_infinity();
416 }
417
430 static void process_msm_transition(TranscriptRow& row, VMState& updated_state, const VMState& state)
431 {
432 if (state.is_accumulator_empty) {
433 updated_state.accumulator = updated_state.msm_accumulator - offset_generator();
434 } else {
435 const Element R = Element(state.accumulator);
436 updated_state.accumulator = R + updated_state.msm_accumulator - offset_generator();
437 }
438 updated_state.is_accumulator_empty = updated_state.accumulator.is_point_at_infinity();
439
440 const Element msm_output = updated_state.msm_accumulator - offset_generator();
441 row.transcript_msm_infinity = msm_output.is_point_at_infinity();
442 }
451 static void normalize_accumulators(Accumulator& accumulator_trace,
452 Accumulator& msm_accumulator_trace,
453 std::vector<Element>& intermediate_accumulator_trace)
454 {
455 Element::batch_normalize(&accumulator_trace[0], accumulator_trace.size());
456 Element::batch_normalize(&msm_accumulator_trace[0], msm_accumulator_trace.size());
457 Element::batch_normalize(&intermediate_accumulator_trace[0], intermediate_accumulator_trace.size());
458 }
469 const Accumulator& accumulator_trace,
470 const Accumulator& msm_accumulator_trace,
471 const Accumulator& intermediate_accumulator_trace)
472 {
473 for (size_t i = 0; i < accumulator_trace.size(); ++i) {
474 TranscriptRow& row = transcript_state[i + 1];
475 if (!accumulator_trace[i].is_point_at_infinity()) {
476 row.accumulator_x = accumulator_trace[i].x;
477 row.accumulator_y = accumulator_trace[i].y;
478 }
479 if (!msm_accumulator_trace[i].is_point_at_infinity()) {
480 row.msm_output_x = msm_accumulator_trace[i].x;
481 row.msm_output_y = msm_accumulator_trace[i].y;
482 }
483 if (!intermediate_accumulator_trace[i].is_point_at_infinity()) {
484 row.transcript_msm_intermediate_x = intermediate_accumulator_trace[i].x;
485 row.transcript_msm_intermediate_y = intermediate_accumulator_trace[i].y;
486 }
487 }
488 }
507 static void compute_inverse_trace_coordinates(const bool msm_transition,
508 const TranscriptRow& row,
509 const Element& msm_output,
510 FF& transcript_msm_x_inverse_trace,
511 Element& msm_accumulator_trace,
512 Element& accumulator_trace,
513 FF& inverse_trace_x,
514 FF& inverse_trace_y)
515 {
516
517 const bool msm_output_infinity = msm_output.is_point_at_infinity();
518 const bool row_msm_infinity = row.transcript_msm_infinity;
519
520 transcript_msm_x_inverse_trace = row_msm_infinity ? 0 : (msm_accumulator_trace.x - offset_generator().x);
521
522 FF lhsx;
523 FF lhsy;
524 if (msm_transition) {
525 lhsx = msm_output_infinity ? 0 : msm_output.x;
526 lhsy = msm_output_infinity ? 0 : msm_output.y;
527 } else {
528 lhsx = row.base_x;
529 lhsy = row.base_y;
530 }
531 const FF rhsx = accumulator_trace.is_point_at_infinity() ? 0 : accumulator_trace.x;
532 const FF rhsy = accumulator_trace.is_point_at_infinity() ? 0 : accumulator_trace.y;
533 inverse_trace_x = lhsx - rhsx;
534 inverse_trace_y = lhsy - rhsy;
535 }
536
571 const ECCVMOperation& entry,
572 const Element& intermediate_accumulator,
573 const Element& accumulator,
574 FF& add_lambda_numerator,
575 FF& add_lambda_denominator)
576 {
577 const Element vm_point = entry.op_code.add ? Element(entry.base_point) : intermediate_accumulator;
578
579 const bool vm_infinity = vm_point.is_point_at_infinity();
580 const bool accumulator_infinity = accumulator.is_point_at_infinity();
581
582 // extract coordinates of the current point in ECCOpQueue
583 const FF vm_x = vm_infinity ? 0 : vm_point.x;
584 const FF vm_y = vm_infinity ? 0 : vm_point.y;
585
586 // extract coordinates of the current accumulator
587 const FF accumulator_x = accumulator_infinity ? 0 : accumulator.x;
588 const FF accumulator_y = accumulator_infinity ? 0 : accumulator.y;
589
590 row.transcript_add_x_equal = (vm_x == accumulator_x) || (vm_infinity && accumulator_infinity);
591 row.transcript_add_y_equal = (vm_y == accumulator_y) || (vm_infinity && accumulator_infinity);
592
593 // compute the numerator and denominator of the slope
594 if ((accumulator_x == vm_x) && (accumulator_y == vm_y) && !vm_infinity && !accumulator_infinity) {
595 // Double case (x1 == x2, y1 == y2)
596 add_lambda_denominator = vm_y + vm_y;
597 add_lambda_numerator = vm_x * vm_x * 3;
598 } else if ((accumulator_x != vm_x) && !vm_infinity && !accumulator_infinity) {
599 // General case (x1 != x2)
600 add_lambda_denominator = accumulator_x - vm_x;
601 add_lambda_numerator = accumulator_y - vm_y;
602 }
603 }
611 static void finalize_transcript(std::vector<TranscriptRow>& transcript_state, const VMState& updated_state)
612 {
613 TranscriptRow& final_row = transcript_state.back();
614 final_row.pc = updated_state.pc;
615 final_row.accumulator_x =
616 updated_state.accumulator.is_point_at_infinity() ? 0 : AffineElement(updated_state.accumulator).x;
617 final_row.accumulator_y =
618 updated_state.accumulator.is_point_at_infinity() ? 0 : AffineElement(updated_state.accumulator).y;
619 final_row.accumulator_not_empty = !updated_state.is_accumulator_empty;
620 }
621};
622} // namespace bb
static void add_affine_coordinates_to_transcript(std::vector< TranscriptRow > &transcript_state, const Accumulator &accumulator_trace, const Accumulator &msm_accumulator_trace, const Accumulator &intermediate_accumulator_trace)
Once the point coordinates are converted from Jacobian to affine coordinates, we populate -coordinate...
static void populate_transcript_row(TranscriptRow &row, const ECCVMOperation &entry, const VMState &state, const uint32_t num_muls, const bool msm_transition, const bool next_not_msm)
Populate the transcript rows with the information parsed after the first iteration over the ECCOpQueu...
static void process_msm_transition(TranscriptRow &row, VMState &updated_state, const VMState &state)
typename std::vector< Element > Accumulator
static void finalize_transcript(std::vector< TranscriptRow > &transcript_state, const VMState &updated_state)
Place the number of the MSMs and the coordinates of the accumualted result in the last row of the tra...
static void process_add(const ECCVMOperation &entry, VMState &updated_state, const VMState &state)
Process addition from the ECCOpQueue.
static std::vector< TranscriptRow > compute_rows(const std::vector< ECCVMOperation > &vm_operations, const uint32_t total_number_of_muls)
Computes the ECCVM transcript rows.
static void compute_inverse_trace_coordinates(const bool msm_transition, const TranscriptRow &row, const Element &msm_output, FF &transcript_msm_x_inverse_trace, Element &msm_accumulator_trace, Element &accumulator_trace, FF &inverse_trace_x, FF &inverse_trace_y)
Compute the difference between the x and y coordinates of two points.
static void normalize_accumulators(Accumulator &accumulator_trace, Accumulator &msm_accumulator_trace, std::vector< Element > &intermediate_accumulator_trace)
Batched conversion of points in accumulators from Jacobian coordinates to affine coordinates .
typename CycleGroup::element Element
static void process_mul(const ECCVMOperation &entry, VMState &updated_state, const VMState &state)
Process scalar multiplication from the ECCOpQueue.
typename CycleGroup::affine_element AffineElement
static AffineElement remove_offset_generator(const AffineElement &other)
static AffineElement offset_generator()
Computes offset_generator group element.
static void compute_lambda_numerator_and_denominator(TranscriptRow &row, const ECCVMOperation &entry, const Element &intermediate_accumulator, const Element &accumulator, FF &add_lambda_numerator, FF &add_lambda_denominator)
If entry is not a point at infinity, compute the slope between the VM entry point and current accumul...
group class. Represents an elliptic curve group element. Group is parametrised by Fq and Fr
Definition group.hpp:36
group_elements::affine_element< Fq, Fr, Params > affine_element
Definition group.hpp:42
static constexpr element point_at_infinity
Definition group.hpp:47
group_elements::element< Fq, Fr, Params > element
Definition group.hpp:41
static constexpr affine_element affine_point_at_infinity
Definition group.hpp:49
Entry point for Barretenberg command-line interface.
group< fq, fr, Bn254G1Params > g1
Definition g1.hpp:33
constexpr std::span< const typename Group::affine_element > get_precomputed_generators()
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
AffineElement base_point
uint32_t value() const
static void batch_invert(std::span< field > coeffs) noexcept