Longfellow ZK 0290cb32
Loading...
Searching...
No Matches
lch14.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_LCH14_H_
16#define PRIVACY_PROOFS_ZK_LIB_GF2K_LCH14_H_
17
18#include <stdio.h>
19
20#include <vector>
21
22#include "util/panic.h"
23
24// The algorithm from [LCH14] following [DP24, Algorithm 2]
25//
26// [LCH14] Sian-Jheng Lin, Wei-Ho Chung, and Yunghsiang S. Han: Novel
27// Polynomial Basis and Its Application to Reed-Solomon Erasure Codes,
28// https://arxiv.org/pdf/1404.3458
29
30// [DP24] Benjamin E. Diamond and Jim Posen, Polylogarithmic Proofs
31// for Multilinears over Binary Towers, https://eprint.iacr.org/2024/504
32
33namespace proofs {
34
35template <class Field>
36class LCH14 {
37 using Elt = typename Field::Elt;
38
39 // only works in binary fields
40 static_assert(Field::kCharacteristicTwo);
41
42 public:
43 static constexpr size_t kSubFieldBits = Field::kSubFieldBits;
44
45 explicit LCH14(const Field &F) : f_(F) {
46 // Compute W_i(\beta_j) for all i, j.
47
48 // We store the unnormalized W_[i][j] = W_i(\beta_j)
49 // in the same memory as the normalized \hat{W}_i(\beta_j), since
50 // the unnormalized values are not needed after normalization.
51
52 // In an attempt to improve clarity, we syntactically distinguish
53 // the unnormalized array W from the normalized array w_hat_,
54 // but one must be mindful that the two names alias to the
55 // same memory locations.
56 auto W = w_hat_;
57
58 // Base case: W_0(X) = X
59 for (size_t j = 0; j < kSubFieldBits; ++j) {
60 W[0][j] = f_.beta(j);
61 }
62
63 // Inductive case: W_{i+1}(X) = W_i(X)(W_i(X)+W_i(\beta_i))
64 for (size_t i = 0; i + 1 < kSubFieldBits; ++i) {
65 for (size_t j = 0; j < kSubFieldBits; ++j) {
66 W[i + 1][j] = f_.mulf(W[i][j], f_.addf(W[i][j], W[i][i]));
67 }
68 }
69
70 // normalized \hat{W}_i(\beta j)
71 for (size_t i = 0; i < kSubFieldBits; ++i) {
72 Elt scale = f_.invertf(W[i][i]);
73 for (size_t j = 0; j < kSubFieldBits; ++j) {
74 w_hat_[i][j] = f_.mulf(scale, W[i][j]);
75 }
76 }
77 }
78
79 // Computation of a single twiddle factor.
80 // Implicit in [LCH14, III.E], explicit in [DP24, Algorithm 2].
81 Elt twiddle(size_t i, size_t u) const {
82 Elt t = f_.zero();
83 for (size_t k = 0; u != 0; ++k, u >>= 1) {
84 if (u & 1) {
85 f_.add(t, w_hat_[i][k]);
86 }
87 }
88 return t;
89 }
90
91 // linear-time computation of all twiddles at the same time
92 void twiddles(size_t i, size_t l, size_t coset, Elt tw[]) const {
93 tw[0] = twiddle(i, coset);
94 for (size_t k = 0; (i + 1) + k < l; ++k) {
95 Elt shift = w_hat_[i][(i + 1) + k];
96 for (size_t u = 0; u < (k1 << k); ++u) {
97 tw[u + (k1 << k)] = f_.addf(tw[u], shift);
98 }
99 }
100 }
101
102 size_t ntwiddles(size_t l) const { return k1 << (l - 1); }
103
104 // Notation from [DP24, Algorithm 2], except that we hardcode R=0
105 // and add the coset parameter.
106 void FFT(size_t l, size_t coset, Elt B[/* n = (1 << l) */]) const {
107 check(l <= kSubFieldBits, "l <= kSubFieldBits");
108
109 if (l > 0) {
110 // space for twiddle factors
111 std::vector<Elt> tw(ntwiddles(l));
112
113 for (size_t i = l; i-- > 0;) {
114 size_t s = k1 << i;
115 twiddles(i, l, coset, &tw[0]);
116 for (size_t u = 0; (u << (i + 1)) < (k1 << l); ++u) {
117 Elt twu = tw[u];
118 for (size_t v = 0; v < s; ++v) {
119 butterfly_fwd(B, (u << (i + 1)) + v, s, twu);
120 }
121 }
122 }
123 }
124 }
125
126 void IFFT(size_t l, size_t coset, Elt B[/* n = (1 << l) */]) const {
127 check(l <= kSubFieldBits, "l <= kSubFieldBits");
128
129 if (l > 0) {
130 // space for twiddle factors
131 std::vector<Elt> tw(ntwiddles(l));
132
133 for (size_t i = 0; i < l; ++i) {
134 size_t s = k1 << i;
135 twiddles(i, l, coset, &tw[0]);
136 for (size_t u = 0; (u << (i + 1)) < (k1 << l); ++u) {
137 Elt twu = tw[u];
138 for (size_t v = 0; v < s; ++v) {
139 butterfly_bwd(B, (u << (i + 1)) + v, s, twu);
140 }
141 }
142 }
143 }
144 }
145
146 void BidirectionalFFT(size_t l, size_t k, Elt B[/* n = (1 << l) */]) const {
147 check(l <= kSubFieldBits, "l <= kSubFieldBits");
148 bidir_recur(/*i=*/l, /*coset=*/0, k, B);
149 }
150
151 // debug access to w_hat_
152 Elt WHat_DEBUG(size_t i, size_t j) const { return w_hat_[i][j]; }
153
154 private:
155 // avoid writing static_cast<size_t>(1) all the time.
156 static constexpr size_t k1 = 1;
157
158 const Field &f_;
159
160 // precomputed [i][j] -> \hat{W}(\beta_j)
161 Elt w_hat_[kSubFieldBits][kSubFieldBits];
162
163 // The algorithm described in Joris van der Hoeven, "The Truncated
164 // Fourier Transform and Applications". This implementation is
165 // based on the pseudo-code from the followup paper "Notes on the
166 // Truncated Fourier Transform", also by Joris van der Hoeven.
167 //
168 // Van der Hoeven considers the classic multiplicative FFT;
169 // here we port the algorithm to the [LCH14] adaptive FFT.
170
171 // Here we call the algorithm the "Bidirectional FFT", because
172 // the algorithm takes a set of points in the "time" domain
173 // and the complementary set of points in the "frequency" domain,
174 // and it flips time and frequency, so the algorithm can be
175 // used to compute the forward and backward transforms, as well
176 // as combinations of the two.
177 //
178 // The literature on the truncated Fourier transforms assumes that
179 // the complementary set of points are implicitly set to zero, and
180 // the main problem is how to avoid storing the zeroes. Our main
181 // problem is not time or space efficiency, but polynomial
182 // interpolation. Given k evaluations of a polynomial of degree <k,
183 // compute the other evaluations up to n=2^l. So we care about both
184 // the unknown nonzero coefficients and the unknown n-k evaluations.
185 void bidir_recur(size_t i, size_t coset, size_t k,
186 Elt B[/* n = (1 << i) */]) const {
187 if (i-- > 0) {
188 size_t s = k1 << i;
189 Elt twu = twiddle(i, coset);
190
191 if (k < s) {
192 for (size_t uv = k; uv < s; ++uv) {
193 butterfly_fwd(B, uv, s, twu);
194 }
195
196 bidir_recur(i, coset, k, B);
197
198 for (size_t uv = 0; uv < k; ++uv) {
199 butterfly_diag(B, uv, s, twu);
200 }
201
202 FFT(i, coset + s, B + s);
203 } else /* k >= s */ {
204 IFFT(i, coset, B);
205
206 for (size_t uv = k - s; uv < s; ++uv) {
207 butterfly_diag(B, uv, s, twu);
208 }
209
210 bidir_recur(i, coset + s, k - s, B + s);
211
212 for (size_t uv = 0; uv < k - s; ++uv) {
213 butterfly_bwd(B, uv, s, twu);
214 }
215 }
216 }
217 }
218
219 inline void butterfly_fwd(Elt B[], size_t uv, size_t s,
220 const Elt &twu) const {
221 f_.add(B[uv], f_.mulf(twu, B[uv + s]));
222 f_.add(B[uv + s], B[uv]);
223 }
224
225 inline void butterfly_bwd(Elt B[], size_t uv, size_t s,
226 const Elt &twu) const {
227 f_.sub(B[uv + s], B[uv]);
228 f_.sub(B[uv], f_.mulf(twu, B[uv + s]));
229 }
230
231 // forward at [uv + s], backward at [uv]
232 inline void butterfly_diag(Elt B[], size_t uv, size_t s,
233 const Elt &twu) const {
234 Elt b1 = B[uv + s];
235 f_.add(B[uv + s], B[uv]);
236 f_.sub(B[uv], f_.mulf(twu, b1));
237 }
238};
239
240} // namespace proofs
241
242#endif // PRIVACY_PROOFS_ZK_LIB_GF2K_LCH14_H_
Definition gf2_128.h:63