Longfellow ZK 0290cb32
Loading...
Searching...
No Matches
sparse.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_ARRAYS_SPARSE_H_
16#define PRIVACY_PROOFS_ZK_LIB_ARRAYS_SPARSE_H_
17
18#include <stddef.h>
19
20#include <algorithm>
21#include <memory>
22#include <vector>
23
24#include "algebra/compare.h"
25#include "algebra/poly.h"
26#include "arrays/affine.h"
27#include "util/panic.h"
28
29namespace proofs {
30// ------------------------------------------------------------
31// Sparse representation of multi-affine functions.
32//
33// This class is mainly used as a reference implementation
34// for testing, and it exposes a similar interface as dense<Field>.
35// Sumcheck has its own specialized "quad" implementation.
36//
37template <class Field>
38class Sparse {
39 using Elt = typename Field::Elt;
40 using T2 = Poly<2, Field>;
41
42 public:
43 // A corner on the sparse hypercube, represented as triple of size_t
44 // and a value. The 3D representation is kind of a guess of how
45 // many bits we'll ever need. Under the theory that "size_t" has
46 // enough bits to index a dense array that fills the address space,
47 // and that the program should support |points| gates, and each gate
48 // has three terminals, then a triple ought to be both necessary and
49 // sufficient.
50 struct corner {
51 size_t p0, p1, p2;
52 Elt v;
53
54 bool eqndx(const corner& y) const {
55 return (p2 == y.p2 && p1 == y.p1 && p0 == y.p0);
56 }
57 bool operator==(const corner& y) const { return eqndx(y) && v == y.v; }
58 bool operator!=(const corner& y) const { return !operator==(y); }
59
60 static bool compare(const corner& x, const corner& y, const Field& F) {
61 if (x.p2 < y.p2) return true;
62 if (x.p2 > y.p2) return false;
63 if (x.p1 < y.p1) return true;
64 if (x.p1 > y.p1) return false;
65 if (x.p0 < y.p0) return true;
66 if (x.p0 > y.p0) return false;
67 return elt_less_than(x.v, y.v, F);
68 }
69 };
70
71 // the index of a point in a sparse array
72 using index_t = size_t;
73
74 index_t n_;
75 std::vector<corner> c_;
76
77 explicit Sparse(index_t n) : n_(n), c_(n) {}
78
79 // no copies, but see clone() below
80 Sparse(const Sparse& y) = delete;
81 Sparse(const Sparse&& y) = delete;
82 Sparse operator=(const Sparse& y) = delete;
83
84 // Nobody should need to clone a sparse array except tests.
85 // Reflect this fact in the name.
86 std::unique_ptr<Sparse> clone_testing_only() const {
87 auto s = std::make_unique<Sparse>(n_);
88 for (index_t i = 0; i < n_; ++i) {
89 s->c_[i] = c_[i];
90 }
91 return s;
92 }
93
94 T2 t2_at_corners(index_t* newi, index_t i, const Field& F) const {
95 // If c_[i] and c_[i+1] have the same (P2, P1), and they differ
96 // by the least-significant bit in P0:
97 if (i + 1 < n_ && //
98 c_[i].p2 == c_[i + 1].p2 && //
99 c_[i].p1 == c_[i + 1].p1 && //
100 (c_[i].p0 >> 1) == (c_[i + 1].p0 >> 1) && //
101 c_[i + 1].p0 == c_[i].p0 + 1) {
102 // we have two corners.
103 *newi = i + 2;
104 return T2{c_[i].v, c_[i + 1].v};
105 } else {
106 // we have one corner and the other one is zero.
107 *newi = i + 1;
108 if ((c_[i].p0 & 1) == 0) {
109 return T2{c_[i].v, F.zero()};
110 } else {
111 return T2{F.zero(), c_[i].v};
112 }
113 }
114 }
115
116 // For a given random number r, the binding operation computes
117 // v[p2, p1, p0] = (1 - r) * v[p2, p1, 2 * p0] + r * v[p2, p1, 2 * p0 + 1]
118 // Note that either the odd or the even element or both may not be actually
119 // present in the sparse array.
120 void bind(const Elt& r, const Field& F) {
121 index_t rd = 0, wr = 0;
122 while (rd < n_) {
123 index_t newrd;
124 T2 f = t2_at_corners(&newrd, rd, F);
125 c_[wr] = corner{.p0 = c_[rd].p0 >> 1,
126 .p1 = c_[rd].p1,
127 .p2 = c_[rd].p2,
128 .v = affine_interpolation(r, f.t_[0], f.t_[1], F)};
129 wr++;
130 rd = newrd;
131 }
132
133 // shrink the array
134 n_ = wr;
135 }
136
137 void bind_all(size_t logv, const Elt r[/*logv*/], const Field& F) {
138 for (size_t v = 0; v < logv; ++v) {
139 bind(r[v], F);
140 }
141 }
142
143 void reshape() {
144 // this function works only if c_[i].p0 == 0 for all i, but
145 // rather than checking them one at the time, keep a giant
146 // bitwise OR and check at the end
147 size_t lost_bits = 0;
148 for (index_t i = 0; i < n_; ++i) {
149 lost_bits |= c_[i].p0;
150 c_[i] = corner{.p0 = c_[i].p1, .p1 = c_[i].p2, .p2 = 0, .v = c_[i].v};
151 }
152 check(lost_bits == 0, "lost_bits == 0");
153 }
154
155 // This method can only be called after full binding; the caller
156 // is responsible for ensuring that pre-condition.
157 Elt scalar() {
158 check(n_ == 1, "n_ == 1");
159 check(c_[0].p0 == 0, "c_[0].p0_ == 0");
160 check(c_[0].p1 == 0, "c_[0].p1_ == 0");
161 check(c_[0].p2 == 0, "c_[0].p2_ == 0");
162 return c_[0].v;
163 }
164
165 void canonicalize(const Field& F) {
166 std::sort(c_.begin(), c_.end(), [&F](const corner& x, const corner& y) {
167 return corner::compare(x, y, F);
168 });
169 return coalesce(F);
170 }
171
172 private:
173 void coalesce(const Field& F) {
174 // Coalesce duplicates.
175 // The (rd,wr)=(0,0) iteration executes the else{} branch and
176 // continues with (1,1), so we start at (1,1) and avoid the
177 // special case for wr-1 at wr=0.
178 index_t wr = 1;
179 for (index_t rd = 1; rd < n_; ++rd) {
180 if (c_[rd].eqndx(c_[wr - 1])) {
181 F.add(c_[wr - 1].v, c_[rd].v);
182 } else {
183 c_[wr] = c_[rd];
184 wr++;
185 }
186 }
187 n_ = wr;
188 }
189};
190} // namespace proofs
191
192#endif // PRIVACY_PROOFS_ZK_LIB_ARRAYS_SPARSE_H_
Definition poly.h:24
Definition sparse.h:38
Definition gf2_128.h:63
Definition sparse.h:50