Longfellow ZK 0290cb32
Loading...
Searching...
No Matches
routing.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_CIRCUITS_LOGIC_ROUTING_H_
16#define PRIVACY_PROOFS_ZK_LIB_CIRCUITS_LOGIC_ROUTING_H_
17
18#include <stddef.h>
19
20#include <algorithm>
21#include <vector>
22
23#include "util/ceildiv.h"
24#include "util/panic.h"
25
26namespace proofs {
27/*
28The Routing class implements circuits that shift an array by a variable number
29of positions. The following table can help pick parameters for a shift:
30
31shift_bit[2][2][1] depth: 2 wires: 6 in: 4 out:2 use:6 ovh:0 t:5 cse:0 notn:7
32unshift_bit[2][2][1] depth: 2 wires: 6 in: 4 out:2 use:6 ovh:0 t:5 cse:0 notn:7
33shift_bit[4][4][1] depth: 3 wires: 17 in: 7 out:4 use:15 ovh:2 t:23 cse:0
34notn:27 unshift_bit[4][4][1] depth: 3 wires: 17 in: 7 out:4 use:15 ovh:2 t:23
35cse:0 notn:27 shift_bit[4][4][2] depth: 3 wires: 19 in: 7 out:4 use:15 ovh:4
36t:23 cse:2 notn:20 unshift_bit[4][4][2] depth: 3 wires: 19 in: 7 out:4 use:15
37ovh:4 t:23 cse:2 notn:20 shift_bit[8][8][1] depth: 4 wires: 41 in: 12 out:8
38use:36 ovh:5 t:70 cse:0 notn:83 unshift_bit[8][8][1] depth: 4 wires: 41 in: 12
39out:8 use:36 ovh:5 t:70 cse:0 notn:83 shift_bit[8][8][2] depth: 4 wires: 44
40in: 12 out:8 use:32 ovh:12 t:64 cse:2 notn:62 unshift_bit[8][8][2] depth: 4
41wires: 44 in: 12 out:8 use:32 ovh:12 t:67 cse:2 notn:68 shift_bit[16][16][1]
42depth: 5 wires: 94 in: 21 out:16 use:85 ovh:9 t:186 cse:0 notn:227
43unshift_bit[16][16][1] depth: 5 wires: 94 in: 21 out:16 use:85 ovh:9 t:186
44cse:0 notn:227 shift_bit[16][16][2] depth: 4 wires: 82 in: 21 out:16 use:61
45ovh:21 t:137 cse:4 notn:147 unshift_bit[16][16][2] depth: 4 wires: 82 in: 21
46out:16 use:61 ovh:21 t:137 cse:4 notn:147 shift_bit[16][16][4] depth: 4
47wires: 94 in: 21 out:16 use:61 ovh:33 t:203 cse:58 notn:255
48unshift_bit[16][16][4] depth: 4 wires: 94 in: 21 out:16 use:61 ovh:33 t:203
49cse:58 notn:255 shift_bit[32][32][1] depth: 6 wires: 212 in: 38 out:32
50use:198 ovh:14 t:463 cse:0 notn:579 unshift_bit[32][32][1] depth: 6 wires: 212
51in: 38 out:32 use:198 ovh:14 t:463 cse:0 notn:579 shift_bit[32][32][2] depth:
525 wires: 184 in: 38 out:32 use:142 ovh:42 t:351 cse:4 notn:405
53unshift_bit[32][32][2] depth: 5 wires: 184 in: 38 out:32 use:142 ovh:42 t:366
54cse:4 notn:435 shift_bit[32][32][4] depth: 5 wires: 193 in: 38 out:32 use:118
55ovh:75 t:371 cse:13 notn:427 unshift_bit[32][32][4] depth: 5 wires: 193 in: 38
56out:32 use:118 ovh:75 t:413 cse:13 notn:511 shift_bit[64][64][1] depth: 7
57wires: 475 in: 71 out:64 use:455 ovh:20 t:1109 cse:0 notn:1411
58unshift_bit[64][64][1] depth: 7 wires: 475 in: 71 out:64 use:455 ovh:20 t:1109
59cse:0 notn:1411 shift_bit[64][64][2] depth: 5 wires: 353 in: 71 out:64
60use:275 ovh:78 t:747 cse:6 notn:922 unshift_bit[64][64][2] depth: 5 wires: 353
61in: 71 out:64 use:275 ovh:78 t:747 cse:6 notn:922 shift_bit[64][64][4] depth:
625 wires: 363 in: 71 out:64 use:223 ovh:140 t:954 cse:22 notn:1319
63unshift_bit[64][64][4] depth: 5 wires: 363 in: 71 out:64 use:223 ovh:140 t:954
64cse:22 notn:1319 shift_bit[128][128][1] depth: 8 wires: 1059 in: 136 out:128
65use:1032 ovh:27 t:2588 cse:0 notn:3331 unshift_bit[128][128][1] depth: 8 wires:
661059 in: 136 out:128 use:1032 ovh:27 t:2588 cse:0 notn:3331
67shift_bit[128][128][2] depth: 6 wires: 808 in: 136 out:128 use:660 ovh:148
68t:1842 cse:6 notn:2332 unshift_bit[128][128][2] depth: 6 wires: 808 in: 136
69out:128 use:660 ovh:148 t:1905 cse:6 notn:2458 shift_bit[128][128][4] depth:
705 wires: 695 in: 136 out:128 use:428 ovh:267 t:2406 cse:69 notn:3686
71unshift_bit[128][128][4] depth: 5 wires: 695 in: 136 out:128 use:428 ovh:267
72t:2826 cse:69 notn:4526 shift_bit[256][256][1] depth: 9 wires: 2348 in: 265
73out:256 use:2313 ovh:35 t:5924 cse:0 notn:7683 unshift_bit[256][256][1] depth:
749 wires: 2348 in: 265 out:256 use:2313 ovh:35 t:5924 cse:0 notn:7683
75shift_bit[256][256][2] depth: 6 wires: 1588 in: 265 out:256 use:1305 ovh:283
76t:3905 cse:8 notn:5153 unshift_bit[256][256][2] depth: 6 wires: 1588 in: 265
77out:256 use:1305 ovh:283 t:3905 cse:8 notn:5153 shift_bit[256][256][4] depth:
785 wires: 1355 in: 265 out:256 use:825 ovh:530 t:6750 cse:116 notn:11309
79unshift_bit[256][256][4] depth: 5 wires: 1355 in: 265 out:256 use:825 ovh:530
80t:6750 cse:116 notn:11309 shift_bit[256][256][8] depth: 5 wires: 1595 in: 265
81out:256 use:825 ovh:770 t:33990 cse:2756 notn:65309 unshift_bit[256][256][8]
82depth: 5 wires: 1595 in: 265 out:256 use:825 ovh:770 t:33990 cse:2756 notn:65309
83shift_bit[512][512][1] depth: 10 wires: 5174 in: 522 out:512 use:5130 ovh:44
84t:13357 cse:0 notn:17411 unshift_bit[512][512][1] depth: 10 wires: 5174 in: 522
85out:512 use:5130 ovh:44 t:13357 cse:0 notn:17411 shift_bit[512][512][2] depth: 7
86wires: 3644 in: 522 out:512 use:3098 ovh:546 t:9289 cse:8 notn:12323
87unshift_bit[512][512][2] depth: 7 wires: 3644 in: 522 out:512 use:3098 ovh:546
88t:9544 cse:8 notn:12833 shift_bit[512][512][4] depth: 6 wires: 3148 in: 522
89out:512 use:2094 ovh:1054 t:11361 cse:33 notn:17462 unshift_bit[512][512][4]
90depth: 6 wires: 3148 in: 522 out:512 use:2094 ovh:1054 t:11361 cse:33 notn:17462
91shift_bit[512][512][8] depth: 6 wires: 3194 in: 522 out:512 use:1618 ovh:1576
92t:18192 cse:224 notn:31029 unshift_bit[512][512][8] depth: 6 wires: 3194 in:
93522 out:512 use:1618 ovh:1576 t:21912 cse:224 notn:38469
94shift_bit[1024][1024][1] depth: 11 wires: 11329 in: 1035 out:1024 use:11275
95ovh:54 t:29751 cse:0 notn:38915 unshift_bit[1024][1024][1] depth: 11 wires:
9611329 in: 1035 out:1024 use:11275 ovh:54 t:29751 cse:0 notn:38915
97shift_bit[1024][1024][2] depth: 7 wires: 7243 in: 1035 out:1024 use:6175
98ovh:1068 t:19547 cse:10 notn:26664 unshift_bit[1024][1024][2] depth: 7 wires:
997243 in: 1035 out:1024 use:6175 ovh:1068 t:19547 cse:10 notn:26664
100shift_bit[1024][1024][4] depth: 6 wires: 6232 in: 1035 out:1024 use:4155
101ovh:2077 t:26989 cse:80 notn:43573 unshift_bit[1024][1024][4] depth: 6 wires:
1026232 in: 1035 out:1024 use:4155 ovh:2077 t:30769 cse:80 notn:51133
103shift_bit[1024][1024][8] depth: 6 wires: 6296 in: 1035 out:1024 use:3179
104ovh:3117 t:52409 cse:332 notn:94285 unshift_bit[1024][1024][8] depth: 6 wires:
1056296 in: 1035 out:1024 use:3179 ovh:3117 t:52409 cse:332 notn:94285
106*/
107template <class Logic>
108class Routing {
109 public:
110 typedef typename Logic::BitW bitW;
111 typedef typename Logic::EltW EltW;
112 const Logic& l_;
113
114 explicit Routing(const Logic& l) : l_(l) {}
115
116 // Set B[i] = A[i + amount], for 0 <= i < k. Note that A and B
117 // are in general of different size.
118 template <class T>
119 void shift(size_t logn, const bitW amount[/*logn*/], size_t k, T B[/*k*/],
120 size_t n, const T A[/*n*/], const T& defaultA,
121 size_t unroll) const {
122 std::vector<T> tmp(n);
123 for (size_t i = 0; i < n; ++i) {
124 tmp[i] = A[i];
125 }
126
127 // Now shift TMP in-place.
128
129 // Counting backwards from logn produces a smaller circuit if one
130 // only cares about a contiguous subset of outputs. E.g. if one
131 // wants the first k outputs the number of wires is O(n log k).
132 size_t l = logn;
133
134 // This funny logic in terms of (target_nrounds, consumed)
135 // attempts to equalize the number of bits consumed per round.
136 // E.g., if logn = 11 and unroll = 7, a naive consumed = unroll
137 // would yield 11 = 7 + 4. Instead, we set target_nrounds = 2,
138 // and consumed is 6 in the first round and 5 in the second round.
139 size_t target_nrounds = ceildiv(logn, unroll);
140
141 while (target_nrounds > 0) {
142 size_t consumed = ceildiv(l, target_nrounds);
143 --target_nrounds;
144
145 l -= consumed;
146 size_t shift = size_t(1) << l;
147 shift_step(consumed, &amount[l], n, k, tmp.data(), shift, defaultA);
148 }
149
150 check(l == 0, "l==0");
151
152 for (size_t i = 0; i < k; ++i) {
153 if (i < n) {
154 B[i] = tmp[i];
155 } else {
156 B[i] = defaultA;
157 }
158 }
159 }
160
161 // Set A[i + amount] = B[i], for 0 <= i < k. Note that A and B
162 // are in general of different size.
163 template <class T>
164 void unshift(size_t logn, const bitW amount[/*logn*/], size_t n, T A[/*n*/],
165 size_t k, const T B[/*k*/], const T& defaultB,
166 size_t unroll) const {
167 // we don't need TMP since we can operate on A directly
168 for (size_t i = 0; i < n; ++i) {
169 if (i < k) {
170 A[i] = B[i];
171 } else {
172 A[i] = defaultB;
173 }
174 }
175
176 size_t l = 0;
177 size_t target_nrounds = ceildiv(logn, unroll);
178 while (target_nrounds > 0) {
179 size_t consumed = ceildiv((logn - l), target_nrounds);
180 --target_nrounds;
181
182 size_t shift = size_t(1) << l;
183 unshift_step(consumed, &amount[l], n, k, A, shift, defaultB);
184
185 l += consumed;
186 }
187 proofs::check(l == logn, "l==logn");
188 }
189
190 template <class T, size_t LOGN>
191 void shift(const typename Logic::template bitvec<LOGN>& amount, size_t k,
192 T B[/*k*/], size_t n, const T A[/*n*/], const T& defaultA,
193 size_t unroll) const {
194 shift(LOGN, &amount[0], k, B, n, A, defaultA, unroll);
195 }
196
197 template <class T, size_t LOGN>
198 void unshift(const typename Logic::template bitvec<LOGN>& amount, size_t n,
199 T A[/*n*/], size_t k, const T B[/*k*/], const T& defaultB,
200 size_t unroll) const {
201 unshift(LOGN, &amount[0], n, A, k, B, defaultB, unroll);
202 }
203
204 private:
205 template <class T>
206 void shift_step(size_t logc, const bitW amount[/*logc*/], size_t n, size_t k,
207 T tmp[/*n*/], size_t shift, const T& defaultA) const {
208 const Logic& L = l_; // shorthand
209 size_t c = size_t(1) << logc;
210
211 // cache the common subexpression amount_is[i]
212 std::vector<bitW> amount_is(c);
213 std::vector<bitW> ibits(logc);
214 for (size_t i = 0; i < c; ++i) {
215 L.bits(logc, ibits.data(), i);
216 amount_is[i] = L.eq(logc, ibits.data(), amount);
217 }
218
219 really_shift(c, amount_is.data(), n, k, tmp, shift, defaultA);
220 }
221
222 template <class T>
223 void unshift_step(size_t logc, const bitW amount[/*logc*/], size_t n,
224 size_t k, T A[/*n*/], size_t shift,
225 const T& defaultB) const {
226 const Logic& L = l_; // shorthand
227 size_t c = size_t(1) << logc;
228
229 // cache the common subexpression amount_is[i]
230 std::vector<bitW> amount_is(c);
231 std::vector<bitW> ibits(logc);
232 for (size_t i = 0; i < c; ++i) {
233 L.bits(logc, ibits.data(), i);
234 amount_is[i] = L.eq(logc, ibits.data(), amount);
235 }
236
237 really_unshift(c, amount_is.data(), n, k, A, shift, defaultB);
238 }
239
240 void really_shift(size_t c, const bitW amount_is[/*c*/], size_t n, size_t k,
241 EltW tmp[/*n*/], size_t shift, const EltW& defaultA) const {
242 const Logic& L = l_; // shorthand
243 for (size_t i = 0; i < n && i < k + shift; ++i) {
244 auto f = [&](size_t j) {
245 if (i + j * shift < n) {
246 return L.lmul(&amount_is[j], tmp[i + j * shift]);
247 } else {
248 return L.lmul(&amount_is[j], defaultA);
249 }
250 };
251
252 tmp[i] = L.add(0, c, f);
253 }
254 }
255
256 void really_unshift(size_t c, const bitW amount_is[/*c*/], size_t n, size_t k,
257 EltW A[/*n*/], size_t shift, const EltW& defaultB) const {
258 const Logic& L = l_; // shorthand
259 for (size_t i = std::min(n, k + c * shift); i-- > 0;) {
260 auto f = [&](size_t j) {
261 if (i >= j * shift) {
262 return L.lmul(&amount_is[j], A[i - j * shift]);
263 } else {
264 return L.lmul(&amount_is[j], defaultB);
265 }
266 };
267
268 A[i] = L.add(0, c, f);
269 }
270 }
271
272 void really_shift(size_t c, const bitW amount_is[/*c*/], size_t n, size_t k,
273 bitW tmp[/*n*/], size_t shift, const bitW& defaultA) const {
274 const Logic& L = l_; // shorthand
275 for (size_t i = 0; i < n && i < k + shift; ++i) {
276 bitW r = L.bit(0);
277 for (size_t j = 0; j < c; ++j) {
278 if (i + j * shift < n) {
279 r = L.lor_exclusive(&r, L.land(&amount_is[j], tmp[i + j * shift]));
280 } else {
281 r = L.lor_exclusive(&r, L.land(&amount_is[j], defaultA));
282 }
283 }
284 tmp[i] = r;
285 }
286 }
287
288 void really_unshift(size_t c, const bitW amount_is[/*c*/], size_t n, size_t k,
289 bitW A[/*n*/], size_t shift, const bitW& defaultB) const {
290 const Logic& L = l_; // shorthand
291 for (size_t i = std::min(n, k + c * shift); i-- > 0;) {
292 bitW r = L.bit(0);
293 for (size_t j = 0; j < c; ++j) {
294 if (i >= j * shift) {
295 r = L.lor_exclusive(&r, L.land(&amount_is[j], A[i - j * shift]));
296 } else {
297 r = L.lor_exclusive(&r, L.land(&amount_is[j], defaultB));
298 }
299 }
300 A[i] = r;
301 }
302 }
303
304 template <size_t W>
305 void really_shift(size_t c, const bitW amount_is[/*c*/], size_t n, size_t k,
306 typename Logic::template bitvec<W> tmp[/*n*/], size_t shift,
307 const typename Logic::template bitvec<W>& defaultA) const {
308 const Logic& L = l_; // shorthand
309 for (size_t i = 0; i < n && i < k + shift; ++i) {
310 for (size_t w = 0; w < W; ++w) {
311 bitW r = L.bit(0);
312 for (size_t j = 0; j < c; ++j) {
313 if (i + j * shift < n) {
314 r = L.lor_exclusive(&r,
315 L.land(&amount_is[j], tmp[i + j * shift][w]));
316 } else {
317 r = L.lor_exclusive(&r, L.land(&amount_is[j], defaultA[w]));
318 }
319 }
320 tmp[i][w] = r;
321 }
322 }
323 }
324
325 template <size_t W>
326 void really_unshift(
327 size_t c, const bitW amount_is[/*c*/], size_t n, size_t k,
328 typename Logic::template bitvec<W> A[/*n*/], size_t shift,
329 const typename Logic::template bitvec<W>& defaultB) const {
330 const Logic& L = l_; // shorthand
331 for (size_t i = std::min(n, k + c * shift); i-- > 0;) {
332 for (size_t w = 0; w < W; ++w) {
333 bitW r = L.bit(0);
334 for (size_t j = 0; j < c; ++j) {
335 if (i >= j * shift) {
336 r = L.lor_exclusive(&r, L.land(&amount_is[j], A[i - j * shift][w]));
337 } else {
338 r = L.lor_exclusive(&r, L.land(&amount_is[j], defaultB[w]));
339 }
340 }
341 A[i][w] = r;
342 }
343 }
344 }
345};
346} // namespace proofs
347
348#endif // PRIVACY_PROOFS_ZK_LIB_CIRCUITS_LOGIC_ROUTING_H_
Definition logic.h:38
Definition logic.h:130