Longfellow ZK 0290cb32

Loading...
Searching...
No Matches
ligero_prover.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_LIGERO_LIGERO_PROVER_H_
16#define PRIVACY_PROOFS_ZK_LIB_LIGERO_LIGERO_PROVER_H_
17
18#include <stddef.h>
19
20#include <algorithm>
21#include <array>
22#include <vector>
23
24#include "algebra/blas.h"
25#include "ligero/ligero_param.h"
26#include "ligero/ligero_transcript.h"
27#include "merkle/merkle_commitment.h"
28#include "random/random.h"
29#include "random/transcript.h"
30#include "util/crypto.h"
31#include "util/panic.h"
32
33 namespace proofs {
34 template <class Field, class InterpolatorFactory>
35 class LigeroProver {
36 using Elt = typename Field::Elt;
37
38 public:
39 explicit LigeroProver(const LigeroParam<Field> &p)
40 : p_(p), mc_(p.block_enc - p.dblock), tableau_(p.nrow * p.block_enc) {}
41
42 // The SUBFIELD_BOUNDARY parameter is kind of a hack.
43 //
44 // Most, but not all, witnesses in W[] are known statically to be in
45 // the subfield of Field, for example because they are bits or
46 // bit-plucked values in the subfield. For zero-knowledge, for
47 // these witnesses, it suffices to choose blinding randomness in the
48 // subfield, which yields a shorter proof since most column openings
49 // are fully in the subfield. The problem is now to distinguish
50 // subfield witnesses from field witnesses.
51 //
52 // In the fullness of time we should have a compiler with typing
53 // information (field vs subfield) of all input wires. For now
54 // we implement the following hack: W[i] is in the subfield for
55 // i < SUBFIELD_BOUNDARY, and in the full field otherwise.
56 // If you don't know better, set SUBFIELD_BOUNDARY = 0 which
57 // trivially works for any input.
58 void commit(LigeroCommitment<Field> &commitment, Transcript &ts,
59 const Elt W[/*p_.nw*/], const size_t subfield_boundary,
60 const LigeroQuadraticConstraint lqc[/*nq*/],
61 const InterpolatorFactory &interpolator, RandomEngine &rng,
62 const Field &F) {
63 // Paranoid check on the SUBFIELD_BOUNDARY correctness condition
64 for (size_t i = 0; i < subfield_boundary; ++i) {
65 check(F.in_subfield(W[i]), "element not in subfield");
66 }
67
68 layout(W, subfield_boundary, lqc, interpolator, rng, F);
69
70 // Merkle commitment
71 auto updhash = [&](size_t j, SHA256 &sha) {
72 LigeroCommon<Field>::column_hash(p_.nrow, &tableau_at(0, j + p_.dblock),
73 p_.block_enc, sha, F);
74 };
75 commitment.root = mc_.commit(updhash, rng);
76
77 // P -> V
78 LigeroTranscript<Field>::write_commitment(commitment, ts);
79 }
80
81 // HASH_OF_LLTERM is a hash of LLTERM provided by the caller. We
82 // could compute the hash locally, but usually LLTERM has a special
83 // structure that makes the computation faster on the caller's side.
84 void prove(LigeroProof<Field> &proof, Transcript &ts, size_t nl,
85 size_t nllterm,
86 const LigeroLinearConstraint<Field> llterm[/*nllterm*/],
87 const LigeroHash &hash_of_llterm,
88 const LigeroQuadraticConstraint lqc[/*nq*/],
89 const InterpolatorFactory &interpolator, const Field &F) {
90 {
91 // P -> V
92 // theorem statement
93 ts.write(hash_of_llterm.bytes, hash_of_llterm.kLength);
94 }
95
96 {
97 std::vector<Elt> u_ldt(p_.nwqrow);
98
99 // V -> P
100 LigeroTranscript<Field>::gen_uldt(&u_ldt[0], p_, ts, F);
101 low_degree_proof(&proof.y_ldt[0], &u_ldt[0], F);
102 }
103
104 {
105 std::vector<Elt> alphal(nl);
106 std::vector<std::array<Elt, 3>> alphaq(p_.nq);
107 std::vector<Elt> A(p_.nwqrow * p_.w);
108
109 // V -> P
110 LigeroTranscript<Field>::gen_alphal(nl, &alphal[0], ts, F);
111 LigeroTranscript<Field>::gen_alphaq(&alphaq[0], p_, ts, F);
112
113 LigeroCommon<Field>::inner_product_vector(&A[0], p_, nl, nllterm, llterm,
114 &alphal[0], lqc, &alphaq[0], F);
115
116 dot_proof(&proof.y_dot[0], &A[0], interpolator, F);
117 }
118
119 {
120 std::vector<Elt> u_quad(p_.nqtriples);
121
122 // V -> P
123 LigeroTranscript<Field>::gen_uquad(&u_quad[0], p_, ts, F);
124 quadratic_proof(&proof.y_quad_0[0], &proof.y_quad_2[0], &u_quad[0], F);
125 }
126
127 {
128 // P -> V
129 ts.write(&proof.y_ldt[0], 1, p_.block, F);
130 ts.write(&proof.y_dot[0], 1, p_.dblock, F);
131 ts.write(&proof.y_quad_0[0], 1, p_.r, F);
132 ts.write(&proof.y_quad_2[0], 1, p_.dblock - p_.block, F);
133 }
134
135 {
136 std::vector<size_t> idx(p_.nreq);
137 // V -> P
138 LigeroTranscript<Field>::gen_idx(&idx[0], p_, ts, F);
139
140 compute_req(proof, &idx[0]);
141
142 mc_.open(proof.merkle, &idx[0], p_.nreq);
143 }
144 }
145
146 private:
147 Elt &tableau_at(size_t i, size_t j) {
148 size_t ld = p_.block_enc;
149 return tableau_[i * ld + j];
150 }
151
152 // fill t_[i, [0,n)] with random elements
153 // If the base_only flag is true, then the random element is chosen from
154 // the base field if F is a field extension.
155 void random_row(size_t i, size_t n, RandomEngine &rng, const Field &F) {
156 for (size_t j = 0; j < n; ++j) {
157 tableau_at(i, j) = rng.elt(F);
158 }
159 }
160
161 void random_subfield_row(size_t i, size_t n, RandomEngine &rng,
162 const Field &F) {
163 for (size_t j = 0; j < n; ++j) {
164 tableau_at(i, j) = rng.subfield_elt(F);
165 }
166 }
167
168 // generate the ILDT and IDOT blinding rows
169 void layout_blinding_rows(const InterpolatorFactory &interpolator,
170 RandomEngine &rng, const Field &F) {
171 {
172 // blinds of size [BLOCK]
173 const auto interp = interpolator.make(p_.block, p_.block_enc);
174
175 // low-degree blinding row
176 random_row(p_.ildt, p_.block, rng, F);
177 interp->interpolate(&tableau_at(p_.ildt, 0));
178 }
179
180 {
181 // blinds of size [DBLOCK]
182 const auto interp = interpolator.make(p_.dblock, p_.block_enc);
183
184 // dot-product blinding row constrained to SUM(W) = 0. First
185 // randomize the dblock:
186 random_row(p_.idot, p_.dblock, rng, F);
187
188 // Then constrain to sum(W) = 0
189 Elt sum = Blas<Field>::dot1(p_.w, &tableau_at(p_.idot, p_.r), 1, F);
190 F.sub(tableau_at(p_.idot, p_.r), sum);
191
192 interp->interpolate(&tableau_at(p_.idot, 0));
193
194 // quadratic-test blinding row constrained to W = 0. First
195 // randomize the entire dblock:
196 random_row(p_.iquad, p_.dblock, rng, F);
197
198 // Then constrain to W = 0
199 Blas<Field>::clear(p_.w, &tableau_at(p_.iquad, p_.r), 1, F);
200
201 interp->interpolate(&tableau_at(p_.iquad, 0));
202 }
203 }
204
205 void layout_witness_rows(const Elt W[/*nw*/], size_t subfield_boundary,
206 const InterpolatorFactory &interpolator,
207 RandomEngine &rng, const Field &F) {
208 const auto interp = interpolator.make(p_.block, p_.block_enc);
209
210 // witness row EXTEND([RANDOM[R], WITNESS[W]], BLOCK)
211 for (size_t i = 0; i < p_.nwrow; ++i) {
212 // TRUE if the entire row is in the subfield
213 bool subfield_only = ((i + 1) * p_.w <= subfield_boundary);
214
215 if (subfield_only) {
216 random_subfield_row(i + p_.iw, p_.r, rng, F);
217 } else {
218 random_row(i + p_.iw, p_.r, rng, F);
219 }
220
221 // Set the WITNESS columns to zero first, and then
222 // overwrite with the witnesses that actually exist
223 Blas<Field>::clear(p_.w, &tableau_at(i + p_.iw, p_.r), 1, F);
224 size_t max_col = std::min(p_.w, p_.nw - i * p_.w);
225 Blas<Field>::copy(max_col, &tableau_at(i + p_.iw, p_.r), 1, &W[i * p_.w],
226 1);
227 interp->interpolate(&tableau_at(i + p_.iw, 0));
228 }
229 }
230
231 void layout_quadratic_rows(const Elt W[/*nw*/],
232 const LigeroQuadraticConstraint lqc[/*nq*/],
233 const InterpolatorFactory &interpolator,
234 RandomEngine &rng, const Field &F) {
235 const auto interp = interpolator.make(p_.block, p_.block_enc);
236
237 // copy the multiplicand witnesses into the quadratic rows
238 size_t iqx = p_.iq;
239 size_t iqy = iqx + p_.nqtriples;
240 size_t iqz = iqy + p_.nqtriples;
241
242 for (size_t i = 0; i < p_.nqtriples; ++i) {
243 random_row(iqx + i, p_.r, rng, F);
244 random_row(iqy + i, p_.r, rng, F);
245 random_row(iqz + i, p_.r, rng, F);
246
247 // clear everything first, then overwrite the witnesses that
248 // actually exist
249 Blas<Field>::clear(p_.w, &tableau_at(iqx + i, p_.r), 1, F);
250 Blas<Field>::clear(p_.w, &tableau_at(iqy + i, p_.r), 1, F);
251 Blas<Field>::clear(p_.w, &tableau_at(iqz + i, p_.r), 1, F);
252
253 for (size_t j = 0; j < p_.w && j + i * p_.w < p_.nq; ++j) {
254 const auto *l = &lqc[j + i * p_.w];
255 check(W[l->z] == F.mulf(W[l->x], W[l->y]),
256 "invalid quadratic constraints");
257 tableau_at(iqx + i, j + p_.r) = W[l->x];
258 tableau_at(iqy + i, j + p_.r) = W[l->y];
259 tableau_at(iqz + i, j + p_.r) = W[l->z];
260 }
261 interp->interpolate(&tableau_at(iqx + i, 0));
262 interp->interpolate(&tableau_at(iqy + i, 0));
263 interp->interpolate(&tableau_at(iqz + i, 0));
264 }
265 }
266
267 void layout(const Elt W[/*nw*/], size_t subfield_boundary,
268 const LigeroQuadraticConstraint lqc[/*nq*/],
269 const InterpolatorFactory &interpolator, RandomEngine &rng,
270 const Field &F) {
271 layout_blinding_rows(interpolator, rng, F);
272 layout_witness_rows(W, subfield_boundary, interpolator, rng, F);
273 layout_quadratic_rows(W, lqc, interpolator, rng, F);
274 }
275
276 void low_degree_proof(Elt y[/*block*/], const Elt u_ldt[/*nwqrow*/],
277 const Field &F) {
278 // ILDT blinding row with coefficient 1
279 Blas<Field>::copy(p_.block, y, 1, &tableau_at(p_.ildt, 0), 1);
280
281 // all witness and quadratic rows with coefficient u_ldt[]
282 for (size_t i = 0; i < p_.nwqrow; ++i) {
283 Blas<Field>::axpy(p_.block, y, 1, u_ldt[i], &tableau_at(i + p_.iw, 0), 1,
284 F);
285 }
286 }
287
288 void dot_proof(Elt y[/*dblock*/], const Elt A[/*nwqrow, w*/],
289 const InterpolatorFactory &interpolator, const Field &F) {
290 const auto interpA = interpolator.make(p_.block, p_.dblock);
291
292 // IDOT blinding row with coefficient 1
293 Blas<Field>::copy(p_.dblock, y, 1, &tableau_at(p_.idot, 0), 1);
294
295 std::vector<Elt> Aext(p_.dblock);
296 for (size_t i = 0; i < p_.nwqrow; ++i) {
297 LigeroCommon<Field>::layout_Aext(&Aext[0], p_, i, &A[0], F);
298 interpA->interpolate(&Aext[0]);
299
300 // Accumulate y += A \otimes W.
301 Blas<Field>::vaxpy(p_.dblock, &y[0], 1, &Aext[0], 1,
302 &tableau_at(i + p_.iw, 0), 1, F);
303 }
304 }
305
306 void quadratic_proof(Elt y0[/*r*/], Elt y2[/*dblock - block*/],
307 const Elt u_quad[/*nqtriples*/], const Field &F) {
308 std::vector<Elt> y(p_.dblock);
309 std::vector<Elt> tmp(p_.dblock);
310
311 // IQUAD blinding row with coefficient 1
312 Blas<Field>::copy(p_.dblock, &y[0], 1, &tableau_at(p_.iquad, 0), 1);
313
314 size_t iqx = p_.iq;
315 size_t iqy = iqx + p_.nqtriples;
316 size_t iqz = iqy + p_.nqtriples;
317
318 for (size_t i = 0; i < p_.nqtriples; ++i) {
319 // y += u_quad[i] * (z[i] - x[i] * y[i])
320
321 // tmp = z[i]
322 Blas<Field>::copy(p_.dblock, &tmp[0], 1, &tableau_at(iqz + i, 0), 1);
323
324 // tmp -= x[i] \otimes y[i]
325 Blas<Field>::vymax(p_.dblock, &tmp[0], 1, &tableau_at(iqx + i, 0), 1,
326 &tableau_at(iqy + i, 0), 1, F);
327
328 // y += u_quad[i] * tmp
329 Blas<Field>::axpy(p_.dblock, &y[0], 1, u_quad[i], &tmp[0], 1, F);
330 }
331
332 // sanity check: the W part of Y is zero
333 bool ok = Blas<Field>::equal0(p_.w, &y[p_.r], 1, F);
334 check(ok, "W part is nonzero");
335
336 // extract the first and last parts
337 Blas<Field>::copy(p_.r, y0, 1, &y[0], 1);
338 Blas<Field>::copy(p_.dblock - p_.block, y2, 1, &y[p_.block], 1);
339 }
340
341 void compute_req(LigeroProof<Field> &proof, const size_t idx[/*nreq*/]) {
342 for (size_t i = 0; i < p_.nrow; ++i) {
343 Blas<Field>::gather(p_.nreq, &proof.req_at(i, 0),
344 &tableau_at(i, p_.dblock), idx);
345 }
346 }
347
348 const LigeroParam<Field> p_; /* safer to make copy */
349 MerkleCommitment mc_;
350 std::vector<Elt> tableau_ /*[nrow, block_enc]*/;
351};
352} // namespace proofs
353
354#endif // PRIVACY_PROOFS_ZK_LIB_LIGERO_LIGERO_PROVER_H_
proofs::MerkleCommitment
Definition merkle_commitment.h:46
proofs::RandomEngine
Definition random.h:32
proofs::SHA256
Definition crypto.h:40
proofs::Transcript
Definition transcript.h:70
proofs::GF2_128::Elt
Definition gf2_128.h:63
proofs::LigeroCommitment
Definition ligero_param.h:310
proofs::LigeroHash
Definition ligero_param.h:442
proofs::LigeroLinearConstraint
Definition ligero_param.h:360
proofs::LigeroParam
Definition ligero_param.h:117
proofs::LigeroProof
Definition ligero_param.h:315
proofs::LigeroQuadraticConstraint
Definition ligero_param.h:368