Longfellow ZK 0290cb32
Loading...
Searching...
No Matches
gf2_128.h
1// Copyright 2025 Google LLC.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#ifndef PRIVACY_PROOFS_ZK_LIB_GF2K_GF2_128_H_
16#define PRIVACY_PROOFS_ZK_LIB_GF2K_GF2_128_H_
17
18#include <stdio.h>
19
20#include <array>
21#include <cstddef>
22#include <cstdint>
23#include <optional>
24#include <utility>
25
26#include "gf2k/gf2poly.h"
27#include "gf2k/sysdep.h"
28#include "util/panic.h"
29
30namespace proofs {
31
33
34template <size_t subfield_log_bits = 4>
35class GF2_128 {
36 // avoid writing static_cast<size_t>(1) all the time.
37 static constexpr size_t k1 = 1;
38
39 public:
40 using TypeTag = BinaryFieldTypeTag;
41
42 // Fast representation of the field element via the system-dependent
43 // SIMD type.
44 using N = gf2_128_elt_t;
45
46 // "Slow" representation of the field element as array of
47 // C++ integral types.
48 using N1 = GF2Poly<2>;
49
50 static constexpr size_t kNPolyEvaluationPoints = 6;
51 static constexpr size_t kLogBits = 7;
52 static constexpr size_t kBits = k1 << kLogBits;
53 static constexpr size_t kBytes = kBits / 8u;
54
55 static constexpr size_t kSubFieldLogBits = subfield_log_bits;
56 static constexpr size_t kSubFieldBits = k1 << kSubFieldLogBits;
57 static constexpr size_t kSubFieldBytes = kSubFieldBits / 8u;
58
59 static_assert(kBits == 8u * kBytes);
60 static_assert(kSubFieldBits == 8u * kSubFieldBytes);
61 static constexpr bool kCharacteristicTwo = true;
62
63 struct Elt {
64 N n;
65
66 Elt() : n{} {}
67 explicit Elt(N n_) : n(n_) {}
68
69 // Don't bother using SIMD instructions for comparisons,
70 // otherwise we have to complicate the sysdep API surface.
71 // Unpack into uint64[2] and compute manually.
72 bool operator==(const Elt& y) const { return unpack() == y.unpack(); }
73 bool operator!=(const Elt& y) const { return !operator==(y); }
74
75 // Returns the coefficient of x^i in the polynomial.
76 uint8_t operator[](size_t i) const {
77 auto n1 = uint64x2_of_gf2_128(n);
78 if (i < 64) {
79 return static_cast<uint8_t>((n1[0] >> i) & 0x1);
80 } else if (i < 128) {
81 return static_cast<uint8_t>((n1[1] >> (i - 64)) & 0x1);
82 } else {
83 return 0;
84 }
85 }
86
87 N1 unpack() const { return N1(uint64x2_of_gf2_128(n)); }
88 };
89
90 explicit GF2_128() {
91 kone_ = of_scalar_field(0b1);
92 kx_ = of_scalar_field(0b10);
93
94 // x^{-1} = x^127 + x^6 + x + 1
95 std::array<uint64_t, 2> invx = {
96 (1ull << 6) | (1ull << 1) | (1ull << 0),
97 (1ull << (127 - 64)),
98 };
99 kinvx_ = of_scalar_field(invx);
100
101 Elt g = subfield_generator();
102 kg_ = g;
103 kinvg_ = invertf(g);
104
105 // basis of the subfield = {1, g, g^2, ...}
106 beta_[0] = one();
107 for (size_t i = 1; i < kSubFieldBits; ++i) {
108 beta_[i] = mulf(beta_[i - 1], g);
109 }
110
111 // Reduce the basis to row-echelon form
112 beta_ref();
113
114 // Evaluation points. We use g^i for these as well
115 poly_evaluation_points_[0] = zero();
116 Elt gi = one();
117 for (size_t i = 1; i < kNPolyEvaluationPoints; ++i) {
118 poly_evaluation_points_[i] = gi;
119 mul(gi, g);
120 }
121
122 for (size_t i = 1; i < kNPolyEvaluationPoints; i++) {
123 for (size_t k = kNPolyEvaluationPoints; k-- > i;) {
124 auto dx =
125 subf(poly_evaluation_points_[k], poly_evaluation_points_[k - i]);
126 check(dx != zero(), "dx != zero()");
127 newton_denominators_[k][i] = invertf(dx);
128 }
129 }
130
131 // basis of counters
132 Elt cgi(g); // = g ^ {2^i}, initially i = 0
133 for (size_t i = 0; i < kSubFieldBits; ++i) {
134 counter_beta_[i] = cgi;
135 mul(cgi, cgi);
136 }
137 }
138
139 GF2_128(const GF2_128&) = delete;
140 GF2_128& operator=(const GF2_128&) = delete;
141
142 // The bits of u are the coordinates with respect to the basis
143 // beta_[] of the subfield.
144 Elt of_scalar(uint64_t u) const {
145 Elt t = zero();
146 for (size_t k = 0; k < kSubFieldBits; ++k, u >>= 1) {
147 if (u & 1) {
148 add(t, beta_[k]);
149 }
150 }
151 check(u == 0, "of_scalar(u), too many bits");
152 return t;
153 }
154
155 Elt of_scalar_field(uint64_t n) const {
156 std::array<uint64_t, 2> u = {n, 0};
157 return of_scalar_field(u);
158 }
159 Elt of_scalar_field(const std::array<uint64_t, 2>& u) const {
160 return Elt(gf2_128_of_uint64x2(u));
161 }
162
163 // The base_only flag is a placeholder that takes meaning when F is an
164 // extension field.
165 std::optional<Elt> of_bytes_field(const uint8_t ab[/* kBytes */],
166 bool base_only = true) const {
167 N1 an = N1::of_bytes(ab);
168 return of_scalar_field(an.u64());
169 }
170
171 void to_bytes_field(uint8_t ab[/* kBytes */], const Elt& x) const {
172 x.unpack().to_bytes(ab);
173 }
174
175 bool in_subfield(Elt e) const {
176 auto eu = solve(e);
177 return eu.first == N1{};
178 }
179
180 std::optional<Elt> of_bytes_subfield(
181 const uint8_t ab[/* kSubFieldBytes */]) const {
182 uint64_t u = 0;
183 for (size_t i = kSubFieldBytes; i-- > 0;) {
184 u <<= 8;
185 u |= ab[i];
186 }
187 return of_scalar(u);
188 }
189
190 void to_bytes_subfield(uint8_t ab[/* kSubFieldBytes */], const Elt& x) const {
191 auto eu = solve(x);
192 check(eu.first == N1{}, "eu.first == N1{}");
193 uint64_t u = eu.second;
194 for (size_t i = 0; i < kSubFieldBytes; ++i) {
195 ab[i] = u & 0xFFu;
196 u >>= 8;
197 }
198 }
199
200 // functional interface
201 Elt addf(const Elt& x, const Elt& y) const {
202 return Elt{gf2_128_add(x.n, y.n)};
203 }
204 Elt subf(const Elt& x, const Elt& y) const {
205 return Elt{gf2_128_add(x.n, y.n)};
206 }
207 Elt mulf(const Elt& x, const Elt& y) const {
208 return Elt{gf2_128_mul(x.n, y.n)};
209 }
210 Elt negf(const Elt& x) const { return x; }
211
212 // two-operands interface
213 void add(Elt& a, const Elt& y) const { a = addf(a, y); }
214 void sub(Elt& a, const Elt& y) const { a = subf(a, y); }
215 void mul(Elt& a, const Elt& y) const { a = mulf(a, y); }
216 void neg(Elt& a) const { /* noop */ }
217 void invert(Elt& a) const { a = invertf(a); }
218
219 Elt zero() const { return Elt{}; }
220 Elt one() const { return kone_; }
221 Elt mone() const { return kone_; }
222 Elt x() const { return kx_; }
223 Elt invx() const { return kinvx_; }
224 Elt g() const { return kg_; }
225 Elt invg() const { return kinvg_; }
226 Elt beta(size_t i) const {
227 check(i < kSubFieldBits, "i < kSubFieldBits");
228 return beta_[i];
229 }
230
231 Elt poly_evaluation_point(size_t i) const {
232 check(i < kNPolyEvaluationPoints, "i < kNPolyEvaluationPoints");
233 return poly_evaluation_points_[i];
234 }
235
236 // return (X[k] - X[k - i])^{-1}, were X[i] is the
237 // i-th poly evalaluation point.
238 Elt newton_denominator(size_t k, size_t i) const {
239 check(k < kNPolyEvaluationPoints, "k < kNPolyEvaluationPoints");
240 check(i <= k, "i <= k");
241 check(k != (k - i), "k != (k - i)");
242 return newton_denominators_[k][i];
243 }
244
245 Elt invertf(Elt x) const {
246 N1 a = x.unpack();
247 // Let POLY be the generator of GF(2^128) as GF(2)[x]/(POLY(x)).
248 // The Euclid algorithm would initialize B = POLY, but we cannot
249 // store POLY in one N1. Instead, we use the invariant that B is
250 // always "odd" throughout the algorithm, and we represent B =
251 // BM1OX * X + 1, or BM1OX = (B - 1) / X. For B = POLY, BM1OX =
252 // 1/X initially.
253 N1 bm1ox = kinvx_.unpack();
254 Elt u = one();
255 Elt v = zero();
256 while (a != N1(0)) {
257 if (a.bit(0) == 0) {
258 a.shiftr(1);
259 byinvx(u);
260 } else {
261 // Now A is "odd". Write A = AM1OX * X + 1. This operation
262 // be done in-place in the A variable, but we use another
263 // name for clarity.
264 N1 am1ox = a;
265 am1ox.shiftr(1);
266
267 // Normalize to the partial order degree(A) >= degree(B).
268 // We use the stronger total order "<" which is consistent
269 // with the partial order that we care about.
270 if (am1ox < bm1ox) {
271 std::swap(am1ox, bm1ox);
272 std::swap(u, v);
273 }
274 am1ox.sub(bm1ox);
275 sub(u, v);
276 byinvx(u);
277 a = am1ox;
278 }
279 }
280 return v;
281 }
282
283 // Type for counters. We represent unsigned integer n as g^n
284 // where g is the generator of the subfield.
285 struct CElt {
286 Elt e;
287
288 bool operator==(const CElt& y) const { return e == y.e; }
289 bool operator!=(const CElt& y) const { return !operator==(y); }
290 };
291 CElt as_counter(uint64_t a) const {
292 // 2^{bits} - 2 fits, 2^{bits} - 1 does not
293 check((a + 1u) != 0, "as_counter() arg too large");
294 check(((a + 1u) >> kSubFieldBits) == 0, "as_counter() arg too large");
295 Elt r(one());
296 for (size_t i = 0; i < kSubFieldBits; ++i) {
297 if ((a >> i) & 1) {
298 mul(r, counter_beta(i));
299 }
300 }
301 return CElt{r};
302 }
303 Elt counter_beta(size_t i) const {
304 check(i < kSubFieldBits, "i < kSubFieldBits");
305 return counter_beta_[i];
306 }
307
308 // Convert a counter into *some* field element such that the counter is
309 // zero (as a counter) iff the field element is zero. Since
310 // n as a counter is g^n, we have ((g^n - 1) = 0) <=> (n = 0)
311 Elt znz_indicator(const CElt& celt) const { return subf(celt.e, one()); }
312
313 private:
314 Elt kone_;
315 Elt kx_;
316 Elt kinvx_; // x^{-1}
317 Elt kg_;
318 Elt kinvg_; // g^{-1}
319 Elt beta_[kSubFieldBits]; // basis of the subfield viewed as a
320 // vector space over GF(2)
321 Elt counter_beta_[kSubFieldBits]; // basis of the multiplicative group
322 // of counters.
323
324 // LU decomposition of beta_, in unpacked format. We store L^{-1}
325 // instead of L, see comments in beta_ref()
326 N1 u_[kSubFieldBits];
327 uint64_t linv_[kSubFieldBits];
328
329 // ldnz_[i] stores the column index of the leading nonzero in u_[i].
330 // This array is in principle redundant, since one can always
331 // reconstruct it from u_, but we cache it for efficiency.
332 size_t ldnz_[kSubFieldBits];
333
334 Elt poly_evaluation_points_[kNPolyEvaluationPoints];
335 Elt newton_denominators_[kNPolyEvaluationPoints][kNPolyEvaluationPoints];
336
337 void byinvx(Elt& u) const { mul(u, kinvx_); }
338
339 Elt subfield_generator() {
340 // Let k = kSubFieldLogBits and n = kLogBits.
341 // Let x be the generator of Field.
342
343 // The generator r of the subfield is then
344 // x^{(2^{2^n}-1)/(2^{2^k}-1)}
345
346 // Compute r via the identity
347 // (2^{2^n}-1)/(2^{2^k}-1) =
348 // (2^{2^k}+1)*(2^{2^(k+1)}+1)*...*(2^{2^(n-1)}+1)
349 Elt r(kx_);
350 for (size_t i = kSubFieldLogBits; i < kLogBits; ++i) {
351 // s <- r^{2^(2^i))
352 Elt s(r);
353 for (size_t j = 0; j < (1u << i); ++j) {
354 mul(s, s);
355 }
356 // r <- r^{2^(2^i)+1)
357 mul(r, s);
358 }
359
360 return r;
361 }
362
363 // beta_ref() is a just a variant of Gaussian elimination, but
364 // because many such variants exist, we now explain the exact
365 // mechanics of the algorithm.
366 //
367 // The problem that we need to solve is the inversion of
368 // of_scalar(): given e = of_scalar(u), solve for u. The constraint
369 // we have is that e and u are arrays of bits, conveniently stored
370 // in uint64_t, and ideally we want to perform parallel bitwise
371 // operations, as opposed to extracting individual bits.
372 //
373 // Consider the following block matrix, or tableau:
374 //
375 // [ B | -I ]
376 // [ ------ ]
377 // [ e | u ]
378 //
379 // Here e and u are reinterpreted as row vectors of GF(2) elements;
380 // I is the identity matrix; B is such that B[i] (the i-th row of b)
381 // is beta(i) (the i-th element of the basis of the subfield), and
382 // beta(i) is interpreted as a row vector of 128 GF(2) elements.
383 // (The minus sign in -I is irrelevant over GF(2), but would be
384 // necessary over other fields.)
385 //
386 // We now postulate that the only allowed operation on the tableau
387 // is "axpy": add one row to another, which we can do efficiently
388 // via bitwise xor.
389 //
390 // of_scalar(u) can be reinterpreted in terms axpy as follows.
391 // Start with a tableau with e=0. Reduce u to 0 via axpy
392 // operations, e.g., for all i such that u[i] = 1, add row i to the
393 // last row. Because this is exactly what of_scalar() does, at the
394 // end of the process we have e = of_scalar(u). Because I is
395 // full-rank, any sequence of axpy's that reduces u to 0 produces
396 // the same e.
397 //
398 // We now want to invert the of_scalar() process. We cannot apply
399 // the axpy operations in of_scalar() in reverse order, because we
400 // don't know u, and thus we don't know which operations
401 // of_scalar(u) would apply. However, because B is a basis, any
402 // sequence of axpy operations that starts with u=0 and reduces e to
403 // 0 reconstructs the same u.
404 //
405 // For lack of a better idea, we choose the following sequence of
406 // axpy operations. First reduce B to row-echelon form via axpy
407 // operations on B, and then reduce e to zero via additional axpy
408 // operations. A matrix U is in row-echelon form if the following
409 // condition holds: i' > i implies that U[i'][ldnz[i]] = 0, where
410 // ldnz[i] is the column index of the leading nonzero in row i.
411 //
412 // Since B is constant, we choose to pre-compute the row-echelon
413 // form of B in beta_ref(), and finish the process in solve() when e
414 // is known, for multiple values of e.
415 //
416 // As it happens, reducing B to row-echelon transforms the -I
417 // in the upper-right block to -L^{-1}, where B=LU is the LU
418 // factorization of B. We don't use this correspondence anywhere
419 // in the code other than in the choice of the name Linv for the block.
420 //
421 void beta_ref() {
422 for (size_t i = 0; i < kSubFieldBits; ++i) {
423 // B in the tableau, becomes U at the end.
424 u_[i] = beta_[i].unpack();
425
426 // -I in the tableau, becomes -L^{-1} at the end.
427 // Ignore the minus sign over GF(2).
428 linv_[i] = (static_cast<uint64_t>(1) << i);
429 }
430
431 // Reduce B to row-echelon form.
432 //
433 // Invariant: B([0,RNK), [0,J)) is already in row-echelon form.
434 // The loop body extends this property to J+1 and possibly RNK+1.
435 size_t rnk = 0;
436 for (size_t j = 0; rnk < kSubFieldBits && j < kBits; ++j) {
437 // find pivot at row >= RNK in column J
438 for (size_t i = rnk; i < kSubFieldBits; ++i) {
439 if (u_[i].bit(j)) {
440 std::swap(u_[rnk], u_[i]);
441 std::swap(linv_[rnk], linv_[i]);
442 goto have_pivot;
443 }
444 }
445 // If we get here there is no pivot. Keep the rank RNK the same
446 // and proceed to the next column ++J
447 continue;
448
449 have_pivot:
450 ldnz_[rnk] = j;
451
452 // Pivot on [rnk][j].
453 for (size_t i1 = rnk + 1; i1 < kSubFieldBits; ++i1) {
454 if (u_[i1].bit(j)) {
455 u_[i1].add(u_[rnk]); // axpy on U
456 linv_[i1] ^= linv_[rnk]; // axpy on Linv
457 }
458 }
459 ++rnk;
460 }
461
462 // the basis is indeed a basis:
463 check(rnk == kSubFieldBits, "rnk == kSubFieldBits");
464 }
465
466 std::pair<N1, uint64_t> solve(const Elt& e) const {
467 uint64_t u = 0;
468 N1 ue = e.unpack();
469 for (size_t rnk = 0; rnk < kSubFieldBits; ++rnk) {
470 size_t j = ldnz_[rnk];
471 if (ue.bit(j)) {
472 ue.add(u_[rnk]);
473 u ^= linv_[rnk];
474 }
475 }
476
477 return std::pair(ue, u);
478 }
479};
480
481} // namespace proofs
482
483#endif // PRIVACY_PROOFS_ZK_LIB_GF2K_GF2_128_H_
Definition gf2poly.h:29
Definition gf2_128.h:32
Definition gf2_128.h:285
Definition gf2_128.h:63