Skip to content

Commit 31a1502

Browse files
author
notnotraju
committed
audited, seems sound except for q_skew not being properly constrained. this will require a new column.
1 parent 4fd4f66 commit 31a1502

File tree

1 file changed

+85
-51
lines changed

1 file changed

+85
-51
lines changed

barretenberg/cpp/src/barretenberg/relations/ecc_vm/ecc_msm_relation_impl.hpp

Lines changed: 85 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ void ECCVMMSMRelationImpl<FF>::accumulate(ContainerOverSubrelations& accumulator
9494
const auto& q_double = View(in.msm_double); // is 1 iff we are at an DOUBLE row in Straus algorithm
9595
const auto& q_double_shift = View(in.msm_double_shift);
9696
const auto& msm_size = View(in.msm_size_of_msm);
97-
// const auto& msm_size_shift = View(in.msm_size_of_msm_shift);
9897
const auto& pc = View(in.msm_pc);
9998
const auto& pc_shift = View(in.msm_pc_shift);
10099
const auto& count = View(in.msm_count);
@@ -153,7 +152,8 @@ void ECCVMMSMRelationImpl<FF>::accumulate(ContainerOverSubrelations& accumulator
153152
* 4. The set of reads into (pc_{count + 3}, round, wnaf_slice_{count + 3}) is constructed when q_add = 1 AND
154153
* q_add4 = 1
155154
*
156-
* To ensure that all q_addi values are correctly set we apply consistency checks to q_add1/q_add2/q_add3/q_add4:
155+
* To ensure that all q_addi values are correctly set we apply consistency/continuity checks to
156+
* q_add1/q_add2/q_add3/q_add4:
157157
* 1. If q_add2 = 1, require q_add1 = 1
158158
* 2. If q_add3 = 1, require q_add2 = 1
159159
* 3. If q_add4 = 1, require q_add3 = 1
@@ -171,9 +171,11 @@ void ECCVMMSMRelationImpl<FF>::accumulate(ContainerOverSubrelations& accumulator
171171
* other times simply want to propagate values. (consider, e.g., when `q_add2 == 0`.) This method returns two
172172
* Accumulators that represent x/y coord of output. Output is either an addition of inputs (if `selector == 1`), or
173173
* xa/ya (if `selector == 0`). Additionally, we require `lambda = 0` if `selector = 0`. The `collision_relation`
174-
* accumulator tracks a subrelation that validates xb != xa. Repeated calls to this method will increase the max
175-
* degree of the Accumulator output: degree of x_out, y_out = max degree of x_a/x_b + 1. 4 Iterations will produce
176-
* an output degree of 5.
174+
* accumulator tracks a subrelation that validates xb != xa.
175+
* Repeated calls to this method will increase the max degree of the Accumulator output:
176+
* deg(x_out) = 1 + max(deg(xa, xb)), deg(y_out) = max(1 + deg(x_out), 1 + deg(ya))
177+
* in our application, we chain together 4 of these with the pattern in such a way that the final x_out will have
178+
* degree 5 and the final y_out will have degree 6.
177179
*/
178180
auto add = [&](auto& xb,
179181
auto& yb,
@@ -183,15 +185,18 @@ void ECCVMMSMRelationImpl<FF>::accumulate(ContainerOverSubrelations& accumulator
183185
auto& selector,
184186
auto& relation,
185187
auto& collision_relation) {
186-
// computation of lambda is valid: if s == 1, then L == (yb - ya) / (xb - xa)
187-
// if s == 0, then L == 0. combining these into a single constraint yields:
188-
// s * (L * (xb - xa - 1) - (yb - ya)) + L = 0
188+
// computation of lambda is valid: if q == 1, then L == (yb - ya) / (xb - xa)
189+
// if q == 0, then L == 0. combining these into a single constraint yields:
190+
// q * (L * (xb - xa - 1) - (yb - ya)) + L = 0
189191
relation += selector * (lambda * (xb - xa - 1) - (yb - ya)) + lambda;
190192
collision_relation += selector * (xb - xa);
191-
// x3 = L.L + (-xb - xa) * q + (1 - q) xa
193+
// x_out = L.L + (-xb - xa) * q + (1 - q) xa
194+
// deg L = 1, deg q = 1, min(deg(xa), deg(xb))≥ 1.
195+
// hence deg(x_out) = 1 + max(deg(xa, xb))
192196
auto x_out = lambda.sqr() + (-xb - xa - xa) * selector + xa;
193197

194-
// y3 = L . (xa - x3) - ya * q + (1 - q) ya
198+
// y_out = L . (xa - x_out) - ya * q + (1 - q) ya
199+
// hence deg(y_out) = max(1 + deg(x_out), 1 + deg(ya))
195200
auto y_out = lambda * (xa - x_out) + (-ya - ya) * selector + ya;
196201
return std::array<Accumulator, 2>{ x_out, y_out };
197202
};
@@ -242,13 +247,13 @@ void ECCVMMSMRelationImpl<FF>::accumulate(ContainerOverSubrelations& accumulator
242247
Accumulator x4_collision_relation(0);
243248
// If `msm_transition == 1`, we have started a new MSM. We need to treat the current value of [Acc] as the point at
244249
// infinity!
245-
auto [x_t1, y_t1] = first_add(acc_x, acc_y, x1, y1, lambda1, msm_transition, add_relation, x1_collision_relation);
246-
auto [x_t2, y_t2] = add(x2, y2, x_t1, y_t1, lambda2, add2, add_relation, x2_collision_relation);
247-
auto [x_t3, y_t3] = add(x3, y3, x_t2, y_t2, lambda3, add3, add_relation, x3_collision_relation);
248-
auto [x_t4, y_t4] = add(x4, y4, x_t3, y_t3, lambda4, add4, add_relation, x4_collision_relation);
250+
auto [x_t1, y_t1] =
251+
first_add(acc_x, acc_y, x1, y1, lambda1, msm_transition, add_relation, x1_collision_relation); // [deg 2, deg 3]
252+
auto [x_t2, y_t2] = add(x2, y2, x_t1, y_t1, lambda2, add2, add_relation, x2_collision_relation); // [deg 3, deg 4]
253+
auto [x_t3, y_t3] = add(x3, y3, x_t2, y_t2, lambda3, add3, add_relation, x3_collision_relation); // [deg 4, deg 5]
254+
auto [x_t4, y_t4] = add(x4, y4, x_t3, y_t3, lambda4, add4, add_relation, x4_collision_relation); // [deg 5, deg 6]
249255

250256
// Validate accumulator output matches ADD output if q_add = 1
251-
// (this is a degree-6 relation)
252257
std::get<0>(accumulator) += q_add * (acc_x_shift - x_t4) * scaling_factor;
253258
std::get<1>(accumulator) += q_add * (acc_y_shift - y_t4) * scaling_factor;
254259
std::get<2>(accumulator) += q_add * add_relation * scaling_factor;
@@ -280,7 +285,8 @@ void ECCVMMSMRelationImpl<FF>::accumulate(ContainerOverSubrelations& accumulator
280285
*
281286
* As with additions, the column q_double describes whether row is a double round. It is Prover-defined.
282287
* The value of `msm_round` can only update when `q_double = 1` and we use this to ensure Prover correctly sets
283-
* `q_double`. (see round transition relations further down)
288+
* `q_double`. The reason for this is that `msm_round` witnesses the wNAF digit we are processing, and we only
289+
* perform the four doublings when we are done processing a wNAF digit. See round transition relations further down.
284290
*/
285291
Accumulator double_relation(0);
286292
auto [x_d1, y_d1] = dbl(acc_x, acc_y, lambda1, double_relation);
@@ -299,6 +305,10 @@ void ECCVMMSMRelationImpl<FF>::accumulate(ContainerOverSubrelations& accumulator
299305
* If scalar slice == 7, we add into accumulator (point_table[7] maps to -[P])
300306
* If scalar slice == 0, we do not add into accumulator
301307
* i.e. for the skew round we can use the slice values as our "selector" when doing conditional point adds
308+
*
309+
* As with addition and doubling, the column q_skew is prover-defined. It is precisely turned on when the round
310+
* is 32. We implement this constraint slightly differently. For more details, see the round transition relations
311+
* below.
302312
*/
303313
Accumulator skew_relation(0);
304314
static FF inverse_seven = FF(7).invert();
@@ -321,7 +331,6 @@ void ECCVMMSMRelationImpl<FF>::accumulate(ContainerOverSubrelations& accumulator
321331
auto [x_s4, y_s4] = add(x4, y4, x_s3, y_s3, lambda4, skew4_select, skew_relation, x4_skew_collision_relation);
322332

323333
// Validate accumulator output matches SKEW output if q_skew = 1
324-
// (this is a degree-6 relation)
325334
std::get<3>(accumulator) += q_skew * (acc_x_shift - x_s4) * scaling_factor;
326335
std::get<4>(accumulator) += q_skew * (acc_y_shift - y_s4) * scaling_factor;
327336
std::get<5>(accumulator) += q_skew * skew_relation * scaling_factor;
@@ -333,7 +342,8 @@ void ECCVMMSMRelationImpl<FF>::accumulate(ContainerOverSubrelations& accumulator
333342
const auto add_second_point = add2 * q_add + q_skew * skew2_select;
334343
const auto add_third_point = add3 * q_add + q_skew * skew3_select;
335344
const auto add_fourth_point = add4 * q_add + q_skew * skew4_select;
336-
// Step 2: construct the delta between x-coordinates for each point add (depending on if row is ADD or SKEW)
345+
// Step 2: construct the difference a.k.a. delta between x-coordinates for each point add (depending on if row is
346+
// ADD or SKEW)
337347
const auto x1_delta = x1_skew_collision_relation * q_skew + x1_collision_relation * q_add;
338348
const auto x2_delta = x2_skew_collision_relation * q_skew + x2_collision_relation * q_add;
339349
const auto x3_delta = x3_skew_collision_relation * q_skew + x3_collision_relation * q_add;
@@ -345,65 +355,83 @@ void ECCVMMSMRelationImpl<FF>::accumulate(ContainerOverSubrelations& accumulator
345355
std::get<9>(accumulator) += (x4_delta * collision_inverse4 - add_fourth_point) * scaling_factor;
346356

347357
// Validate that if q_add = 1 or q_skew = 1, add1 also is 1
348-
// TODO(@zac-williamson) Once we have a stable base to work off of, remove q_add1 and replace with q_msm_add +
349-
// q_msm_skew (issue #2222)
358+
// Optimize(@zac-williamson): could just get rid of add1 as a column, as it is a linear combination, see issue #2222
350359
std::get<32>(accumulator) += (add1 - q_add - q_skew) * scaling_factor;
351360

352-
// If add_i = 0, slice_i = 0
353361
// When add_i = 0, force slice_i to ALSO be 0
354362
std::get<13>(accumulator) += (-add1 + 1) * slice1 * scaling_factor;
355363
std::get<14>(accumulator) += (-add2 + 1) * slice2 * scaling_factor;
356364
std::get<15>(accumulator) += (-add3 + 1) * slice3 * scaling_factor;
357365
std::get<16>(accumulator) += (-add4 + 1) * slice4 * scaling_factor;
358366

359-
// only one of q_skew, q_double, q_add can be nonzero
367+
// SELECTORS ARE MUTUALLY EXCLUSIVE
368+
// at most one of q_skew, q_double, q_add can be nonzero
360369
std::get<17>(accumulator) += (q_add * q_double + q_add * q_skew + q_double * q_skew) * scaling_factor;
361370

362-
// We look up wnaf slices by mapping round + pc -> slice
363-
// We use an exact set membership check to validate that
364-
// wnafs written in wnaf_relation == wnafs read in msm relation
365-
// We use `add1/add2/add3/add4` to flag whether we are performing a wnaf read op
366-
// We can set these to be Prover-defined as the set membership check implicitly ensures that the correct reads
367-
// have occurred.
368-
// if msm_transition = 0, round_shift - round = 0 or 1
369-
const auto round_delta = round_shift - round;
371+
// ROUND TRANSITION LOGIC
372+
// `round_transition` describes whether we are transitioning between "rounds" of the MSM according to the Straus
373+
// algorithm. In particular, the `round` corresponds to the wNAF digit we are currently processing.
370374

371-
// ROUND TRANSITION LOGIC (when round does not change)
372-
// If msm_transition = 0 (next row) then round_delta = 0 or 1
375+
const auto round_delta = round_shift - round;
376+
// If `msm_transition == 0` (next row) then `round_delta` is boolean; the round is internal to a given MSM and
377+
// represents the wNAF digit currently being processed. `round_delta == 0` means that the current and next steps of
378+
// the Straus algorithm are processing the same wNAF digit place.
379+
380+
// `round_transition == 0` if `round_delta == 0` or the next row is an MSM transition.
381+
// if `round_transition != 1`, then `round_transition == round_delta == 1` by the following constraint.
382+
// in particular, `round_transition` is boolean. (`round_delta` is not boolean precisely one step before an MSM
383+
// transition, but that does not concern us.)
373384
const auto round_transition = round_delta * (-msm_transition_shift + 1);
374385
std::get<18>(accumulator) += round_transition * (round_delta - 1) * scaling_factor;
375386

376-
// ROUND TRANSITION LOGIC (when round DOES change)
377-
// round_transition describes whether we are transitioning between rounds of an MSM
378-
// If round_transition = 1, the next row is either a double (if round != 31) or we are adding skew (if round ==
379-
// 31) round_transition * skew * (round - 31) = 0 (if round tx and skew, round == 31) round_transition * (skew +
380-
// double - 1) = 0 (if round tx, skew XOR double = 1) i.e. if round tx and round != 31, double = 1
387+
// If `round_transition == 1`, then `round_delta == 1` and `msm_transition_shift == 0`. Therefore, the
388+
// next row in the VM is either a double (if `round != 31`) or skew (if `round == 31`). In either case, the point is
389+
// that we have finished processing a wNAF digit place and need to either perform the doublings to move on to the
390+
// next place _or_ we are at the last place and need to perform the skew computation to finish. These are
391+
// equationally represented as:
392+
// round_transition * skew_shift * (round - 31) = 0 (if round tx and skew, then round == 31);
393+
// round_transition * (skew_shift + double_shift - 1) = 0 (if round tx, then skew XOR double = 1).
394+
// together, these have the following implications: if round tx and round != 31, then double_shift = 1.
395+
// conversely, if round tx and double_shift == 0, then `skew_shift == 1`, which then forces `round == 31`.
396+
//
397+
// NOTE(@notnotraju): we must also constrain q_skew == 1 when round == 32. As far as I can tell, this is not done
398+
// anywhere. Here is the potential bad thing that could happen: we could set q_skew to be 0 after the only place
399+
// where it is forced (at round transition). Or worse, we could alternative q_skew and double in this round 32. As
400+
// far as I can tell, this requires adding an extra column, which bears witness to the fact that the round is 32.
381401
std::get<19>(accumulator) += round_transition * q_skew_shift * (round - 31) * scaling_factor;
382402
std::get<20>(accumulator) += round_transition * (q_skew_shift + q_double_shift - 1) * scaling_factor;
383403

384-
// if no double or no skew, round_delta = 0
404+
// if the next is neither double nor skew, and we are not at an msm_transition, then round_delta = 0 and the next
405+
// "row" of our VM is processing the same wNAF digit place.
385406
std::get<21>(accumulator) += round_transition * (-q_double_shift + 1) * (-q_skew_shift + 1) * scaling_factor;
386407

387-
// if double, next double != 1
388-
std::get<22>(accumulator) += q_double * q_double_shift * scaling_factor;
408+
// if double, next add = 1. As q_double, q_add, and q_skew are mutually exclusive, this suffices to force
409+
// q_double_shift == q_skew_shift == 0.
410+
std::get<22>(accumulator) += q_double * (-q_add_shift + 1) * scaling_factor;
389411

390-
// if double, next add = 1
391-
std::get<23>(accumulator) += q_double * (-q_add_shift + 1) * scaling_factor;
412+
// UPDATING THE COUNT
392413

393-
// updating count
394-
// if msm_transition = 0 and round_transition = 0, count_shift = count + add1 + add2 + add3 + add4
395-
// todo: we need this?
414+
// if we are changing the "round" (i.e. starting to process a new wNAF digit), the count_shift must be 0.
415+
std::get<23>(accumulator) += round_delta * count_shift * scaling_factor;
416+
// if msm_transition = 0 and round_transition = 0, then the next "row" of the VM is processing the same wNAF digit.
417+
// this means that the count must increase: count_shift = count + add1 + add2 + add3 + add4
396418
std::get<24>(accumulator) += (-msm_transition_shift + 1) * (-round_delta + 1) *
397419
(count_shift - count - add1 - add2 - add3 - add4) * scaling_factor;
398420

421+
// at least one of the following must be true:
422+
// the next step is an MSM transition;
423+
// the next count is zero (meaning we are starting the processing of a new wNAF digit)
424+
// the next step is processing the same wNAF digit (i.e., round_delta == 0)
425+
// (note that at the start of a new MSM, the count is also zero, so the above are not mutually exclusive.)
399426
std::get<25>(accumulator) +=
400427
is_not_first_row * (-msm_transition_shift + 1) * round_delta * count_shift * scaling_factor;
401428

402-
// if msm_transition = 1, count_shift = 0
403-
std::get<26>(accumulator) += is_not_first_row * msm_transition_shift * count_shift * scaling_factor;
429+
// if msm_transition = 1, then count = 0 (as we are starting a new MSM and hence a new wNAF
430+
// digit)
431+
std::get<26>(accumulator) += msm_transition * count * scaling_factor;
404432

405-
// if msm_transition = 1, pc = pc_shift + msm_size
406-
// `ecc_set_relation` ensures `msm_size` maps to `transcript.msm_count` for the current value of `pc`
433+
// if msm_transition_shift = 1, pc = pc_shift + msm_size
434+
// NB: `ecc_set_relation` ensures `msm_size` maps to `transcript.msm_count` for the current value of `pc`
407435
std::get<27>(accumulator) += is_not_first_row * msm_transition_shift * (msm_size + pc_shift - pc) * scaling_factor;
408436

409437
// Addition continuity checks
@@ -413,8 +441,7 @@ void ECCVMMSMRelationImpl<FF>::accumulate(ContainerOverSubrelations& accumulator
413441
// Case 3: add4 = 1, add3 = 0
414442
// These checks ensure that the current row does not skip points (for both ADD and SKEW ops)
415443
// This is part of a wider set of checks we use to ensure that all point data is used in the assigned
416-
// multiscalar multiplication operation.
417-
// (and not in a different MSM operation)
444+
// multiscalar multiplication operation (and not in a different MSM operation).
418445
std::get<28>(accumulator) += add2 * (-add1 + 1) * scaling_factor;
419446
std::get<29>(accumulator) += add3 * (-add2 + 1) * scaling_factor;
420447
std::get<30>(accumulator) += add4 * (-add3 + 1) * scaling_factor;
@@ -433,6 +460,13 @@ void ECCVMMSMRelationImpl<FF>::accumulate(ContainerOverSubrelations& accumulator
433460
// when transition occurs, perform set membership lookup on (accumulator / pc / msm_size)
434461
// perform set membership lookups on add_i * (pc / round / slice_i)
435462
// perform lookups on (pc / slice_i / x / y)
463+
464+
// We look up wnaf slices by mapping round + pc -> slice
465+
// We use an exact set membership check to validate that
466+
// wnafs written in wnaf_relation == wnafs read in msm relation
467+
// We use `add1/add2/add3/add4` to flag whether we are performing a wnaf read op
468+
// We can set these to be Prover-defined as the set membership check implicitly ensures that the correct reads
469+
// have occurred.
436470
}
437471

438472
} // namespace bb

0 commit comments

Comments
 (0)