Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
pippenger_constantine.test.cpp
Go to the documentation of this file.
1// Unit tests for the Constantine signed-Booth window recoder used by the
2// round-parallel Pippenger MSM. Validates the scalar packed-digit recoder,
3// the SIMD x4 specialisations (Localised / Bottom / Boundary), and the
4// round-trip identity `Σ_w (-1)^sign_w · bucket_w · 2^{B_w} ≡ scalar`.
5
7
11
12#include <array>
13#include <cstdint>
14#include <gtest/gtest.h>
15#include <vector>
16
17namespace {
18
20using ScalarField = bb::fr;
22
23constexpr size_t LIMB_BITS_U64 = 64;
24constexpr size_t NUM_LIMBS_U64 = 4;
25constexpr size_t NUM_LIMBS_U32 = 8;
26constexpr size_t MAX_BITS = 256;
27
28// =============================================================================
29// Reference signed-window encoder. Reads `(window_bits + 1)` bits from the
30// scalar starting at `bit_offset - 1` (with a synthetic 0 at bit -1 when
31// bit_offset == 0), then applies the signed-Booth encode:
32//
33// raw = bits [bit_offset-1, bit_offset + window_bits)
34// neg = raw >> window_bits (top bit = sign indicator)
35// encode = (raw + 1) >> 1 (drop the lookback bit)
36// bucket = (encode - neg) ^ (-neg) (conditional negate, branchless)
37// packed = (neg << 31) | bucket
38//
39// Same algebra as `get_constantine_packed_digit`, but implemented in the most
40// obvious way against a flat `bit_at(i)` accessor so any error in the
41// production path's limb-walking or branchless conditional negate will diverge.
42// =============================================================================
43uint32_t reference_packed_digit(const uint64_t* scalar_data, size_t bit_offset, size_t window_bits)
44{
45 auto bit_at = [&](int64_t i) -> uint64_t {
46 if (i < 0 || static_cast<size_t>(i) >= MAX_BITS) {
47 return 0;
48 }
49 return (scalar_data[static_cast<size_t>(i) / LIMB_BITS_U64] >> (static_cast<size_t>(i) % LIMB_BITS_U64)) &
50 uint64_t{ 1 };
51 };
52 uint32_t raw = 0;
53 for (size_t k = 0; k <= window_bits; ++k) {
54 const int64_t bit_idx = static_cast<int64_t>(bit_offset) + static_cast<int64_t>(k) - 1;
55 raw |= static_cast<uint32_t>(bit_at(bit_idx)) << k;
56 }
57 const uint32_t neg = (raw >> window_bits) & 1U;
58 const uint32_t val_mask = (uint32_t{ 1 } << window_bits) - 1;
59 const uint32_t encode = (raw + 1) >> 1;
60 const uint32_t bucket = ((encode - neg) ^ (uint32_t{ 0 } - neg)) & val_mask;
61 return (neg << 31) | bucket;
62}
63
64// Random non-Montgomery scalar — uniform over [0, modulus). We invoke the
65// recoder against the raw limbs so the random_element form is irrelevant; what
66// matters is that the limb bytes are arbitrary.
67std::array<uint64_t, NUM_LIMBS_U64> random_scalar_limbs()
68{
70 for (size_t i = 0; i < NUM_LIMBS_U64; ++i) {
71 out[i] = engine.get_random_uint64();
72 }
73 return out;
74}
75
76// View the same scalar as a uint32 limb array (little-endian: x86/ARM/WASM all
77// agree). The SIMD x4 helpers index by uint32 limbs.
78const uint32_t* as_u32(const std::array<uint64_t, NUM_LIMBS_U64>& s)
79{
80 return reinterpret_cast<const uint32_t*>(s.data());
81}
82
83// Drive `get_constantine_packed_digit` via the params returned by
84// `compute_constantine_slice_params`. The hot loop in Stage 1 / Stage 4 unpacks
85// the struct into scalar values; we mirror that call shape exactly so a future
86// API change here would be caught.
87uint32_t production_scalar_path(const uint64_t* scalar_data, size_t bit_offset, size_t window_bits)
88{
89 const auto sp = cnst::compute_constantine_slice_params(bit_offset, window_bits, NUM_LIMBS_U64);
90 return cnst::get_constantine_packed_digit(scalar_data,
91 sp.lo_limb,
92 sp.hi_limb,
93 sp.lo_off,
94 sp.lo_bits,
95 sp.lo_mask,
96 sp.hi_mask,
97 sp.slice_localised_to_one_u64,
98 window_bits);
99}
100
101// Drive the 4-wide SIMD specialisations by classifying the slice path and
102// calling the matching `store_constantine_packed_digits_x4_*` helper. Out[i]
103// is the packed digit for the i-th scalar. Mirrors Stage 1's per-window
104// dispatch loop in `scalar_multiplication.cpp`.
105void production_simd_path(const std::array<uint64_t, NUM_LIMBS_U64> scalars[4],
106 size_t bit_offset,
107 size_t window_bits,
108 uint32_t out[4])
109{
110 const auto sp = cnst::compute_constantine_slice_params_u32(bit_offset, window_bits, NUM_LIMBS_U32);
111 const cnst::SimdU32x4 lo_mask_v{ sp.lo_mask, sp.lo_mask, sp.lo_mask, sp.lo_mask };
112 const cnst::SimdU32x4 hi_mask_v{ sp.hi_mask, sp.hi_mask, sp.hi_mask, sp.hi_mask };
113 const cnst::SimdU32x4 one_v{ 1, 1, 1, 1 };
114 const uint32_t val_mask_scalar = (uint32_t{ 1 } << window_bits) - 1;
115 const cnst::SimdU32x4 val_mask{ val_mask_scalar, val_mask_scalar, val_mask_scalar, val_mask_scalar };
116
117 const uint32_t* s0 = as_u32(scalars[0]);
118 const uint32_t* s1 = as_u32(scalars[1]);
119 const uint32_t* s2 = as_u32(scalars[2]);
120 const uint32_t* s3 = as_u32(scalars[3]);
121
122 const uint32_t wb_u32 = static_cast<uint32_t>(window_bits);
123 switch (cnst::classify_slice_path_u32(sp)) {
124 case cnst::ConstantineSlicePath::Localised:
126 out, s0, s1, s2, s3, sp.lo_limb, sp.lo_off, lo_mask_v, one_v, val_mask, wb_u32);
127 break;
128 case cnst::ConstantineSlicePath::Bottom:
130 out, s0, s1, s2, s3, sp.hi_limb, sp.lo_bits, hi_mask_v, one_v, val_mask, wb_u32);
131 break;
132 case cnst::ConstantineSlicePath::Boundary:
134 s0,
135 s1,
136 s2,
137 s3,
138 sp.lo_limb,
139 sp.hi_limb,
140 sp.lo_off,
141 sp.lo_bits,
142 lo_mask_v,
143 hi_mask_v,
144 one_v,
145 val_mask,
146 wb_u32);
147 break;
148 }
149}
150
151} // namespace
152
153// =============================================================================
154// Test 1 — Scalar packed-digit recoder matches the textbook reference oracle
155// across all `(window_bits, bit_offset)` pairs the live pipeline ever issues.
156// =============================================================================
157TEST(PippengerConstantine, ScalarMatchesReferenceOracleAllWindowBits)
158{
159 constexpr size_t TRIALS_PER_SHAPE = 32;
160 // window_bits range covers production: choose_window_bits returns 2..19,
161 // build_var_window_schedule's final window can additionally be 1 bit wide
162 // (e.g. wb=3 over 256 bits yields 85*3 + 1). bit_offset 255 covers the
163 // above-modulus top edge where every read bit is structurally zero.
164 for (size_t window_bits = 1; window_bits <= 19; ++window_bits) {
165 for (size_t bit_offset = 0; bit_offset <= 255; ++bit_offset) {
166 for (size_t t = 0; t < TRIALS_PER_SHAPE; ++t) {
167 const auto s = random_scalar_limbs();
168 const uint32_t got = production_scalar_path(s.data(), bit_offset, window_bits);
169 const uint32_t want = reference_packed_digit(s.data(), bit_offset, window_bits);
170 ASSERT_EQ(got, want) << "window_bits=" << window_bits << " bit_offset=" << bit_offset << " trial=" << t;
171 }
172 }
173 }
174}
175
176// =============================================================================
177// Test 2 — SIMD x4 path agrees with the scalar path lane-by-lane across all
178// three specialisations (Localised / Bottom / Boundary). Each bit_offset
179// implicitly selects which specialisation runs; we sweep every offset so all
180// three are exercised.
181// =============================================================================
182TEST(PippengerConstantine, SimdX4MatchesScalarPathLanewise)
183{
184 constexpr size_t TRIALS_PER_SHAPE = 16;
185 bool saw_localised = false;
186 bool saw_bottom = false;
187 bool saw_boundary = false;
188 // window_bits range covers production: choose_window_bits returns 2..19,
189 // build_var_window_schedule's final window can additionally be 1 bit wide
190 // (e.g. wb=3 over 256 bits yields 85*3 + 1). bit_offset 255 covers the
191 // above-modulus top edge where every read bit is structurally zero.
192 for (size_t window_bits = 1; window_bits <= 19; ++window_bits) {
193 for (size_t bit_offset = 0; bit_offset <= 255; ++bit_offset) {
194 const auto sp_u32 = cnst::compute_constantine_slice_params_u32(bit_offset, window_bits, NUM_LIMBS_U32);
195 switch (cnst::classify_slice_path_u32(sp_u32)) {
196 case cnst::ConstantineSlicePath::Localised:
197 saw_localised = true;
198 break;
199 case cnst::ConstantineSlicePath::Bottom:
200 saw_bottom = true;
201 break;
202 case cnst::ConstantineSlicePath::Boundary:
203 saw_boundary = true;
204 break;
205 }
206 for (size_t t = 0; t < TRIALS_PER_SHAPE; ++t) {
208 random_scalar_limbs(), random_scalar_limbs(), random_scalar_limbs(), random_scalar_limbs()
209 };
210 alignas(16) std::array<uint32_t, 4> got_simd{};
211 production_simd_path(scalars.data(), bit_offset, window_bits, got_simd.data());
212 for (size_t lane = 0; lane < 4; ++lane) {
213 const uint32_t want = production_scalar_path(scalars[lane].data(), bit_offset, window_bits);
214 ASSERT_EQ(got_simd[lane], want)
215 << "window_bits=" << window_bits << " bit_offset=" << bit_offset << " lane=" << lane;
216 }
217 }
218 }
219 }
220 // The sweep must exercise all three specialisations or the SIMD coverage is
221 // a no-op for a path. (Coverage check, not a behavioural claim.)
222 EXPECT_TRUE(saw_localised);
223 EXPECT_TRUE(saw_bottom);
224 EXPECT_TRUE(saw_boundary);
225}
226
227// =============================================================================
228// Test 3 — Round-trip identity. For any tiled window schedule covering
229// [0, total_bits) bits, the sum `Σ_w (-1)^sign_w · bucket_w · 2^{B_w}` must
230// equal the scalar value modulo 2^total_bits. This is the load-bearing
231// algebraic invariant the whole MSM rests on; if it ever fails the rest of
232// the pipeline silently mis-computes the result.
233// =============================================================================
234TEST(PippengerConstantine, RoundTripIdentityMatchesScalarMod2N)
235{
236 constexpr size_t TOTAL_BITS = 254;
237 constexpr size_t TRIALS = 64;
238 // Including window_bits == 1 because `build_var_window_schedule` truncates
239 // the final window to whatever bits remain, which can be exactly 1.
240 for (size_t window_bits = 1; window_bits <= 19; ++window_bits) {
241 for (size_t t = 0; t < TRIALS; ++t) {
242 const auto s = random_scalar_limbs();
243 // Recover scalar value as a 256-bit big integer (4 × uint64).
244 // We reconstruct it limb-by-limb using __int128 arithmetic so the
245 // round-trip is plainly readable; production code uses field
246 // arithmetic, which we deliberately avoid here.
247 //
248 // Tile windows of width `window_bits` until we cover TOTAL_BITS+2
249 // bits. The +2 mirrors the `total_bits = num_bits + 2` budget used
250 // by `build_var_window_schedule` to absorb the carry-less top bit.
251 std::vector<std::pair<int32_t, size_t>> signed_digits; // (signed_value, bit_offset)
252 size_t bit_offset = 0;
253 size_t bits_remaining = TOTAL_BITS + 2;
254 while (bits_remaining > 0) {
255 const size_t wb = std::min(window_bits, bits_remaining);
256 const uint32_t packed = production_scalar_path(s.data(), bit_offset, wb);
257 const uint32_t neg = packed >> 31;
258 const uint32_t bucket = packed & ((uint32_t{ 1 } << wb) - 1);
259 const int32_t signed_val = (neg != 0U) ? -static_cast<int32_t>(bucket) : static_cast<int32_t>(bucket);
260 signed_digits.emplace_back(signed_val, bit_offset);
261 bit_offset += wb;
262 bits_remaining -= wb;
263 }
264
265 // Reconstruct: Σ_w signed_val_w · 2^{bit_offset_w} mod 2^256, using
266 // uint256_t arithmetic where signed subtraction is just `acc -= |v| << off`.
268 for (const auto& [v, off] : signed_digits) {
269 const bb::numeric::uint256_t shifted = bb::numeric::uint256_t(static_cast<uint64_t>(v < 0 ? -v : v))
271 if (v < 0) {
272 acc -= shifted;
273 } else {
274 acc += shifted;
275 }
276 }
277 const bb::numeric::uint256_t scalar_val(s[0], s[1], s[2], s[3]);
278 EXPECT_EQ(acc, scalar_val) << "window_bits=" << window_bits << " trial=" << t;
279 }
280 }
281}
282
283// =============================================================================
284// Test 4 — Edge cases. Pin the structural boundaries explicitly so a regression
285// at one of them (rather than at a random bit) shows up as a named failure.
286// =============================================================================
287TEST(PippengerConstantine, EdgeCases)
288{
289 // (a) Zero scalar — every packed digit must be 0 (sign 0, bucket 0).
290 // Sweep includes wb=1 (final-window truncation) and bit_offset=255
291 // (above-modulus top edge — every bit read is structurally zero).
293 for (size_t wb = 1; wb <= 19; ++wb) {
294 for (size_t off = 0; off <= 255; ++off) {
295 EXPECT_EQ(production_scalar_path(zero.data(), off, wb), uint32_t{ 0 })
296 << "zero scalar wb=" << wb << " off=" << off;
297 }
298 }
299
300 // (b) Bottom window — bit_offset == 0 must select the synthetic-zero
301 // lookback path. The classifier flags it via `is_bottom_window`.
302 const auto sp_bottom = cnst::compute_constantine_slice_params_u32(0, 12, NUM_LIMBS_U32);
303 EXPECT_TRUE(sp_bottom.is_bottom_window);
304 EXPECT_EQ(cnst::classify_slice_path_u32(sp_bottom), cnst::ConstantineSlicePath::Bottom);
305
306 // (c) Top window — when the natural hi_limb read lands past the scalar's
307 // storage, the production code clamps `hi_limb` and zeros `hi_mask`. The
308 // packed digit must still match the reference oracle (which extends with
309 // zeros above bit 256). Sweep all the way to bit_offset=255 to cover the
310 // above-modulus case where every read bit is structurally zero.
311 auto top_aligned = random_scalar_limbs();
312 constexpr size_t window_bits = 12;
313 for (size_t bit_offset = 240; bit_offset <= 255; ++bit_offset) {
314 const uint32_t got = production_scalar_path(top_aligned.data(), bit_offset, window_bits);
315 const uint32_t want = reference_packed_digit(top_aligned.data(), bit_offset, window_bits);
316 EXPECT_EQ(got, want) << "top window bit_offset=" << bit_offset;
317 }
318
319 // (d) Localised fast path — the c+1-bit window must fit inside a single
320 // uint64 limb for the localised path to be selected. With window_bits=12
321 // and bit_offset=10, the lookback bit is at limb 0, bit 9; the window
322 // spans bits 10..21 — all inside limb 0, so localised path fires.
323 const auto sp_local = cnst::compute_constantine_slice_params(10, 12, NUM_LIMBS_U64);
324 EXPECT_TRUE(sp_local.slice_localised_to_one_u64);
325
326 // (e) Boundary case — when the window straddles a uint64 boundary the
327 // localised flag must be false. With window_bits=12 and bit_offset=60,
328 // the window spans bits 59..71 → crosses bit 63→64.
329 const auto sp_boundary = cnst::compute_constantine_slice_params(60, 12, NUM_LIMBS_U64);
330 EXPECT_FALSE(sp_boundary.slice_localised_to_one_u64);
331}
332
333// =============================================================================
334// Test 5 — Named slice-shape table. Random sweeps probably hit every
335// (limb_index, slice_path) combination, but a regression at one of these
336// boundaries (e.g. "boundary across bit 31→32, lookback in lo half") shows up
337// as a named failure here rather than an opaque "trial 17 of 32" log line.
338//
339// `bit_offset` here is the absolute bit position of the FIRST window bit; the
340// lookback bit lives at `bit_offset - 1`. Each row pins the (bit_offset, wb)
341// pair, the expected slice path under u32 indexing, and the expected
342// localisation under u64 indexing.
343// =============================================================================
344TEST(PippengerConstantine, NamedSliceShapes)
345{
346 struct ShapeCase {
347 const char* name;
348 size_t bit_offset;
349 size_t window_bits;
351 bool u64_localised; // expected `slice_localised_to_one_u64`
352 };
353 // Picked so each row exercises a structurally distinct shape:
354 // - bottom_* : synthetic-lookback path
355 // - local_* : c+1 bits fit inside a single u64 limb (and matching u32)
356 // - boundary_* : window straddles a u64 or u32 limb boundary
357 // - top_clamped : hi_limb would land past scalar storage → clamp + zero mask
358 const std::array<ShapeCase, 12> cases{ {
359 // Bottom — bit_offset 0 across several wb.
360 { "bottom_wb12", 0, 12, cnst::ConstantineSlicePath::Bottom, false },
361 { "bottom_wb2", 0, 2, cnst::ConstantineSlicePath::Bottom, false },
362 { "bottom_wb19", 0, 19, cnst::ConstantineSlicePath::Bottom, false },
363 // Localised — lookback + window inside a single u32 (and therefore a single u64).
364 { "local_lo_u32", 10, 12, cnst::ConstantineSlicePath::Localised, true },
365 // Localised in u64 but boundary in u32 — lookback at bit 30 (u32 limb 0), window spans bits 30..42
366 // (crosses u32 bit 31→32) but stays inside u64 limb 0.
367 { "local_u64_boundary_u32", 31, 12, cnst::ConstantineSlicePath::Boundary, true },
368 // Boundary across u64 bit 63→64.
369 { "boundary_u64_at_63", 60, 12, cnst::ConstantineSlicePath::Boundary, false },
370 { "boundary_u64_at_127", 124, 12, cnst::ConstantineSlicePath::Boundary, false },
371 { "boundary_u64_at_191", 188, 12, cnst::ConstantineSlicePath::Boundary, false },
372 // Boundary at u32 bit 31→32 with lookback in low half.
373 { "boundary_u32_at_31", 30, 4, cnst::ConstantineSlicePath::Boundary, true },
374 // Top window — clamp regime. With wb=12, bit_offset=246 reads bits 245..257; hi limb is past
375 // the scalar's 256-bit storage in u32 view (limb_index 7 is the last).
376 { "top_clamped_wb12", 246, 12, cnst::ConstantineSlicePath::Boundary, false },
377 // wb=1 at the very top — the final-window case `build_var_window_schedule` can emit.
378 { "top_wb1_final", 254, 1, cnst::ConstantineSlicePath::Localised, true },
379 // Random mid-scalar localised case as a "happy path" anchor.
380 { "local_mid_u64", 80, 12, cnst::ConstantineSlicePath::Localised, true },
381 } };
382
383 auto s = random_scalar_limbs();
384 for (const auto& c : cases) {
385 const auto sp_u32 = cnst::compute_constantine_slice_params_u32(c.bit_offset, c.window_bits, NUM_LIMBS_U32);
386 const auto sp_u64 = cnst::compute_constantine_slice_params(c.bit_offset, c.window_bits, NUM_LIMBS_U64);
387 EXPECT_EQ(cnst::classify_slice_path_u32(sp_u32), c.u32_path) << "case=" << c.name;
388 EXPECT_EQ(sp_u64.slice_localised_to_one_u64, c.u64_localised) << "case=" << c.name;
389
390 // The encoder must still produce the reference value at each named shape.
391 const uint32_t got = production_scalar_path(s.data(), c.bit_offset, c.window_bits);
392 const uint32_t want = reference_packed_digit(s.data(), c.bit_offset, c.window_bits);
393 EXPECT_EQ(got, want) << "case=" << c.name;
394 }
395}
396
397// =============================================================================
398// Test 6 — u64 / u32 param classifier internal consistency.
399//
400// The scalar path uses `ConstantineSliceParams` (u64-indexed); the SIMD path
401// uses `ConstantineSliceParamsU32` (u32-indexed). Comparing final packed
402// digits (Test 1+2) catches END-to-END divergence, but a compensating bug
403// across the two param computations could mask itself. This test asserts the
404// param structs encode the SAME lookback bit position and the SAME read width
405// where their definitions agree, so a bug in one classifier alone shows up
406// even if the digits happen to round-trip.
407// =============================================================================
408TEST(PippengerConstantine, ParamClassifierU64U32Consistency)
409{
410 for (size_t wb = 1; wb <= 19; ++wb) {
411 for (size_t bit_offset = 0; bit_offset <= 255; ++bit_offset) {
412 const auto sp_u64 = cnst::compute_constantine_slice_params(bit_offset, wb, NUM_LIMBS_U64);
413 const auto sp_u32 = cnst::compute_constantine_slice_params_u32(bit_offset, wb, NUM_LIMBS_U32);
414
415 // Bottom-window classification: both must agree (u64 signals via lo_mask==0,
416 // u32 via the explicit is_bottom_window flag).
417 const bool u64_says_bottom = (sp_u64.lo_mask == 0);
418 EXPECT_EQ(u64_says_bottom, sp_u32.is_bottom_window)
419 << "bottom classification disagrees at bit_offset=" << bit_offset << " wb=" << wb;
420
421 // Lookback bit absolute position: lo_limb·LIMB_BITS + lo_off. Both views must
422 // identify the same absolute bit (skip bottom, where the lookback is synthetic
423 // and the limb/offset encoding is intentionally not a real position).
424 if (!sp_u32.is_bottom_window) {
425 const size_t u64_lookback = sp_u64.lo_limb * 64 + sp_u64.lo_off;
426 const size_t u32_lookback = sp_u32.lo_limb * 32 + sp_u32.lo_off;
427 EXPECT_EQ(u64_lookback, u32_lookback)
428 << "lookback bit disagrees at bit_offset=" << bit_offset << " wb=" << wb;
429 EXPECT_EQ(u64_lookback, bit_offset - 1)
430 << "lookback bit ≠ bit_offset-1 at bit_offset=" << bit_offset << " wb=" << wb;
431 }
432
433 // Localised-flag implication: u64-localised means the whole c+1 window lives in
434 // one u64 limb. That does NOT imply u32-localised (window could still straddle
435 // a u32 boundary inside the same u64), but it DOES imply the u32 view's slice
436 // path is NOT Bottom (bit_offset > 0 cases only).
437 if (sp_u64.slice_localised_to_one_u64 && bit_offset > 0) {
438 EXPECT_NE(cnst::classify_slice_path_u32(sp_u32), cnst::ConstantineSlicePath::Bottom)
439 << "u64-localised but u32 classifier says Bottom at bit_offset=" << bit_offset << " wb=" << wb;
440 }
441 }
442 }
443}
numeric::RNG & engine
RNG & get_randomness()
Definition engine.cpp:258
uint32_t get_constantine_packed_digit(const uint64_t *scalar_data, uint32_t lo_limb, uint32_t hi_limb, uint32_t lo_off, uint32_t lo_bits, uint32_t lo_mask, uint32_t hi_mask, bool slice_localised_to_one_u64, size_t window_bits) noexcept
Read (window_bits+1) bits from scalar_data (uint64 limbs) using precomputed slice params and apply Co...
ConstantineSlicePath classify_slice_path_u32(const ConstantineSliceParamsU32 &sp) noexcept
void store_constantine_packed_digits_x4_bottom(uint32_t *dst, const uint32_t *scalar_data_0, const uint32_t *scalar_data_1, const uint32_t *scalar_data_2, const uint32_t *scalar_data_3, uint32_t hi_limb, uint32_t lo_bits, SimdU32x4 hi_mask_v, SimdU32x4 one_v, SimdU32x4 val_mask, uint32_t window_bits) noexcept
void store_constantine_packed_digits_x4_boundary(uint32_t *dst, const uint32_t *scalar_data_0, const uint32_t *scalar_data_1, const uint32_t *scalar_data_2, const uint32_t *scalar_data_3, uint32_t lo_limb, uint32_t hi_limb, uint32_t lo_off, uint32_t lo_bits, SimdU32x4 lo_mask_v, SimdU32x4 hi_mask_v, SimdU32x4 one_v, SimdU32x4 val_mask, uint32_t window_bits) noexcept
uint32_t __attribute__((vector_size(16))) SimdU32x4
ConstantineSliceParams compute_constantine_slice_params(size_t bit_offset, size_t window_bits, size_t num_uint64_limbs) noexcept
void store_constantine_packed_digits_x4_localised(uint32_t *dst, const uint32_t *scalar_data_0, const uint32_t *scalar_data_1, const uint32_t *scalar_data_2, const uint32_t *scalar_data_3, uint32_t lo_limb, uint32_t lo_off, SimdU32x4 lo_mask_v, SimdU32x4 one_v, SimdU32x4 val_mask, uint32_t window_bits) noexcept
ConstantineSliceParamsU32 compute_constantine_slice_params_u32(size_t bit_offset, size_t window_bits, size_t num_u32_limbs) noexcept
field< Bn254FrParams > fr
Definition fr.hpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
TEST(PippengerConstantine, ScalarMatchesReferenceOracleAllWindowBits)
std::byte * data