Longfellow ZK 0290cb32
Loading...
Searching...
No Matches
ligero_param.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_PARAM_H_
16#define PRIVACY_PROOFS_ZK_LIB_LIGERO_LIGERO_PARAM_H_
17
18#include <stddef.h>
19
20#include <algorithm>
21#include <array>
22#include <cstdint>
23#include <cstring>
24#include <vector>
25
26#include "algebra/blas.h"
27#include "merkle/merkle_commitment.h"
28#include "merkle/merkle_tree.h"
29#include "util/ceildiv.h"
30#include "util/crypto.h"
31#include "util/panic.h"
32
33/*
34
35 This is an implementation of the Ligero protocol described in
36
37 Ligero: Lightweight Sublinear Arguments
38 Without a Trusted Setup,
39
40 Scott Ames and Carmit Hazay and Yuval Ishai and
41 Muthuramakrishnan Venkitasubramaniam,
42 https://eprint.iacr.org/2022/1608
43 doi = {10.1145/3133956},
44
45 The main data structure in the prover is a 2D array which we call a
46 tableau organized as follows.
47
48 Fix a block size BLOCK and let DBLOCK = 2 * BLOCK - 1. Fix another
49 quantity BLOCK_EXT >= 0.
50
51 Each row in the tableau has the form [X XD XEXT], where X is a row
52 of BLOCK elements, XD is a row of BLOCK - 1 elements, and XEXT is a
53 row of BLOCK_EXT elements. We call the X part the "block" and the
54 XEXT part the "extension".
55
56 Let BLOCK_ENC = 2 * BLOCK - 1 + BLOCK_EXT = DBLOCK + BLOCK_EXT be
57 the total size of the row.
58
59 A "witness block" has the form [RANDOM[R], WITNESS[W]], where R + W
60 = BLOCK. The randomess (of size R) is used for zero-knowledge
61 blinding. Although not strictly required by Ligero, we require W >=
62 R to avoid wasting too much space, so that a witness block is at
63 least half full.
64
65 A block is interpreted as evaluations of some polynomial at point
66 INJ(j) for 0 <= j < BLOCK, where INJ(.) is some field-specific
67 injection that injects small natural numbers into distinct field
68 elements. With the condition that the degree of the polynomial be
69 less than BLOCK, the polynomial is uniquely determined, and the rest
70 [XD XEXT] of the row is then computed as the evaluations of that
71 polynomial for BLOCK <= j < BLOCK_ENC.
72
73 To the extent that Ligero is based on Reed-Solomon codes, X is the
74 "message" and XEXT is the "codeword". The "rate" is thus BLOCK /
75 BLOCK_EXT.
76
77 However, Ligero also needs products of two polynomials of degree
78 less than BLOCK, so that the product has degree less than 2 * BLOCK
79 - 1 = DBLOCK. XD exists in the tableau to facilitate the
80 computation of these products. For zero knowledge, the indices of
81 XD must be distinct from the indices of BLOCK_EXT.
82
83 We now discuss the row structure of the tableau. The first three
84 rows are special and used for zero-knowledge blinding purposes.
85
86 The first row, row ILDT for ILDT = 0, used for the low-degree test,
87 consists of BLOCK random field elements, extended to BLOCK_ENC.
88
89 The second row, row IDOT for IDOT = 1, used in the linear test,
90 consists of DBLOCK random field elements, with the additional
91 constraint that the double block sum to 0. As usual, the row is
92 extended to BLOCK_ENC by interpolation.
93
94 The third row, row IQUAD for IQUAD = 2, used in the quadratic test,
95 consists of DBLOCK random field elements, with the additional
96 constraint that the WITNESS portion of the block be zero. Thus, the
97 structure is really [RANDOM[R] ZERO[W] RANDOM[BLOCK-1]], extended to
98 BLOCK_ENC by interpolation.
99
100 The next group of "witness rows" IW <= I < IQ for IW = 3, stores
101 witnesses. Each row is a witness block extended to BLOCK_ENC.
102
103 The next group of "quadratic" rows IQ <= I < NROW, has the same
104 syntactic structure as the "witness" rows, but they are used in the
105 quadratic check in addition to the linear check. In Ligero, a
106 quadratic constraint induces three entries in three quadratic rows.
107 Thus, for NQ total quadratic constraints and W useful entries per
108 row, we have a total of 3 * (NQ / W) quadratic rows. To enforce
109 this structure, the code stores NQTRIPLES = (NQ / W) instead of the
110 number 3 * NQTRIPLES of rows.
111
112 */
113
114namespace proofs {
115
116template <class Field>
117struct LigeroParam {
118 using Elt = typename Field::Elt;
119
120 // parameters passed by the user
121 size_t nw; // total number of witnesses
122 size_t nq; // total number of quadratic constraints
123 size_t rateinv; // inverse rate of the error-correcting code
124 size_t nreq; // number of opened columns
125
126 // computed parameters
127 size_t block_enc; // total number of elts per row
128 size_t block; // number of elts per block
129 size_t dblock; // 2 * BLOCK - 1
130 size_t block_ext; // BLOCK_ENC - DBLOCK (number of leaves in the
131 // Merkle tree).
132 size_t r; // number of random elts in a witness block
133 size_t w; // number of witnesses in a witness block
134 size_t nwrow; // number of witness rows
135 size_t nqtriples; // number of triples of quadratic-check rows
136 size_t nwqrow; // nwqrow + nqtriples
137 size_t nrow; // total number of rows (nwqrow + three blinding rows)
138 size_t mc_pathlen; // length of a Merkle-tree proof
139 // with BLOCK_ENC-BLOCK leaves
140
141 // layout of rows
142 size_t ildt; // blinding for the low-degree test
143 size_t idot; // blinding row for the dot-product check
144 size_t iquad; // blinding row for the quadratic check
145 size_t iw; // first witness row
146 size_t iq; // first quadratic row
147
148 LigeroParam(size_t nw, size_t nq, size_t rateinv, size_t nreq)
149 : nw(nw), nq(nq), rateinv(rateinv), nreq(nreq) {
150 r = nreq;
151
152 size_t min_proof_size = SIZE_MAX;
153 size_t best_block_enc = 1;
154 for (size_t e = 1; e <= (1 << 28); e *= 2) {
155 size_t proof_size = layout(e);
156 if (proof_size < min_proof_size) {
157 min_proof_size = proof_size;
158 best_block_enc = e;
159 }
160 }
161
162 // recompute parameters
163 layout(best_block_enc);
164 proofs::check(block_enc > block, "block_enc > block");
165
166 ildt = 0;
167 idot = 1;
168 iquad = 2;
169 iw = 3;
170 iq = iw + nwrow;
171 proofs::check(nrow == iq + 3 * nqtriples, "nrow == iq + 3 * nqtriples");
172 }
173
174 private:
175 // Return an estimate of the proof size.
176 //
177 // This function is kind of a hack in that it breaks abstraction
178 // boundaries, e.g. it knows about the size and layout of the Merkle
179 // commitment. Punt on this wart until we have a better theory.
180 size_t layout(size_t e) {
181 // Maximum size we are prepared to handle. All dimensions will be
182 // required to be < MAX_SIZE. In principle we could handle all
183 // size_t, but we want 64-bit code to fail if it would fail on a
184 // 32-bit machine, and for maximum paranoia we restrict to 28
185 // bits, since one cannot malloc 2^{28} Elts on a 32-bit machine
186 // anyway.
187 constexpr size_t max_lg_size = 28;
188 constexpr size_t max_size = static_cast<size_t>(1) << max_lg_size;
189 block_enc = e;
190
191 // block_enc must fit in the subfield
192 size_t subfield_bits = 8 * Field::kSubFieldBytes;
193 if (subfield_bits <= max_lg_size) {
194 if (block_enc >= (static_cast<size_t>(1) << subfield_bits)) {
195 return SIZE_MAX;
196 }
197 }
198
199 // limit block_enc to avoid overflow in the computation
200 // of the proof size
201 if (block_enc > max_size || rateinv > max_size ||
202 (block_enc + 1) < (2 + rateinv)) {
203 return SIZE_MAX;
204 }
205
206 block = (block_enc + 1) / (2 + rateinv);
207 // now 1 <= BLOCK < MAX_SIZE / 2
208
209 // Ensure BLOCK = R + W (syntactic property)
210 if (block < r) {
211 return SIZE_MAX;
212 }
213 w = block - r;
214
215 // now r <= BLOCK < MAX_SIZE / 2
216 // 0 <= W < MAX_SIZE / 2
217 // 0 <= W <= BLOCK
218 // 0 <= R <= BLOCK
219 // W + R == BLOCK
220
221 // Ensure W >= R (needed for reasonable space utilization).
222 if (w < r) {
223 return SIZE_MAX;
224 }
225 // now R <= W < MAX_SIZE
226
227 // Finish the layout of a row
228 dblock = 2 * block - 1;
229 // now DBLOCK < MAX_SIZE
230
231 // Ensure BLOCK_ENC >= 0 (syntactic property). Should be true
232 // for any reasonable rateinv, but check anyway.
233 if (block_enc < dblock) {
234 return SIZE_MAX;
235 }
236 // now DBLOCK <= BLOCK_ENC
237
238 block_ext = block_enc - dblock;
239 // now 0 <= BLOCK_EXT < MAX_SIZE
240
241 nwrow = ceildiv(nw, w);
242 nqtriples = ceildiv(nq, w);
243
244 nwqrow = nwrow + 3 * nqtriples;
245 nrow = nwqrow + /*blinding rows=*/3;
246
247 // The total number of elements (NROW * BLOCK_ENC) in the tableau
248 // must fit in MAX_SIZE.
249 if (nrow >= max_size / block_enc) {
250 return SIZE_MAX;
251 }
252
253 mc_pathlen = merkle_commitment_len(block_ext);
254
255 /* proof+commitment size. */
256 // Compute the size in uint64_t instead of size_t since
257 // I am too lazy to worry about overflow.
258 uint64_t sz = 0;
259
260 // commitment
261 sz += sizeof(Digest);
262
263 // Merkle openings, approximated because the exact # of leaves depends
264 // on the random coins.
265 sz += static_cast<uint64_t>(mc_pathlen) / 2 * static_cast<uint64_t>(nreq) *
266 static_cast<uint64_t>(Digest::kLength);
267
268 // y_ldt
269 sz += static_cast<uint64_t>(block) * static_cast<uint64_t>(Field::kBytes);
270
271 // y_dot
272 sz += static_cast<uint64_t>(dblock) * static_cast<uint64_t>(Field::kBytes);
273
274 // y_quad
275 // The quadratic-test response has size DBLOCK, but W elements
276 // are expected to be zero and not serialized.
277 sz += static_cast<uint64_t>(dblock - w) *
278 static_cast<uint64_t>(Field::kBytes);
279
280 // nonces
281 sz += static_cast<uint64_t>(nreq) *
282 static_cast<uint64_t>(MerkleNonce::kLength);
283
284 // req. Assume optimistically that all elements are in the subfield.
285 sz += static_cast<uint64_t>(nrow) * static_cast<uint64_t>(nreq) *
286 static_cast<uint64_t>(Field::kSubFieldBytes);
287
288 sz = std::min<uint64_t>(sz, SIZE_MAX);
289 return static_cast<size_t>(sz);
290 }
291};
292
293template <class Field>
295 Digest root;
296};
297
298template <class Field>
299struct LigeroProof {
300 using Elt = typename Field::Elt;
301 explicit LigeroProof(const LigeroParam<Field> *p)
302 : block(p->block),
303 dblock(p->dblock),
304 r(p->r),
305 block_enc(p->block_enc),
306 nrow(p->nrow),
307 nreq(p->nreq),
308 mc_pathlen(p->mc_pathlen),
309 y_ldt(p->block),
310 y_dot(p->dblock),
311 y_quad_0(p->r),
312 y_quad_2(p->dblock - p->block),
313 req(p->nrow * p->nreq),
314 merkle(p->nreq) {}
315
316 // The proof stores a copy of all parameters relevant to the proof.
317 size_t block;
318 size_t dblock;
319 size_t r;
320 size_t block_enc;
321 size_t nrow;
322 size_t nreq;
323 size_t mc_pathlen;
324
325 std::vector<Elt> y_ldt; // [block]
326 std::vector<Elt> y_dot; // [dblock]
327 std::vector<Elt> y_quad_0; // [r] first part of y_quad.
328 // The middle part [w] of y_quad is zero and not transmitted.
329 std::vector<Elt> y_quad_2; // [dblock - block] last part of y_quad
330 std::vector<Elt> req; // [nrow, nreq]
331 MerkleProof merkle;
332
333 Elt &req_at(size_t i, size_t j) { return req[i * nreq + j]; }
334 const Elt &req_at(size_t i, size_t j) const { return req[i * nreq + j]; }
335};
336
337// a nonzero entry in the matrix A that defines
338// the linear constraints A w = b. The term
339// states that A[c, w] = k, where the "row"
340// c is interpreted as the constraint index, and
341// the "column" w is interpreted as the witness
342// index
343template <class Field>
345 using Elt = typename Field::Elt;
346 size_t c;
347 size_t w;
348 Elt k;
349};
350
351// encode W[X] * W[Y] - W[Z] = 0
353 size_t x;
354 size_t y;
355 size_t z;
356};
357
358template <class Field>
360 using Elt = typename Field::Elt;
361
362 public:
363 // create a grand dot product by A given the user-provided
364 // linear-constraint terms LLTERM, the quadratic constraints LQC,
365 // and their random challenges ALPHAL, ALPHAQ.
366 static void inner_product_vector(
367 Elt A[/*nwqrow, w*/], const LigeroParam<Field> &p, size_t nl,
368 size_t nllterm, const LigeroLinearConstraint<Field> llterm[/*nllterm*/],
369 const Elt alphal[/*nl*/], const LigeroQuadraticConstraint lqc[/*nq*/],
370 const std::array<Elt, 3> alphaq[/*nq*/], const Field &F) {
371 // clear A and overwrite it later.
372 Blas<Field>::clear(p.nwqrow * p.w, A, 1, F);
373
374 // random linear combinations of the linear constraints
375 for (size_t l = 0; l < nllterm; ++l) {
376 const auto &term = llterm[l];
377 proofs::check(term.w < p.nw, "term.w < p.nw");
378 proofs::check(term.c < nl, "term.c < nl");
379 F.add(A[term.w], F.mulf(term.k, alphal[term.c]));
380 }
381
382 // routing terms for quadratic constraints
383 Elt *Ax = &A[p.nwrow * p.w];
384 Elt *Ay = Ax + (p.nqtriples * p.w);
385 Elt *Az = Ay + (p.nqtriples * p.w);
386
387 for (size_t i = 0; i < p.nqtriples; ++i) {
388 for (size_t j = 0; j < p.w && j + i * p.w < p.nq; ++j) {
389 // index into [_ , W] arrays
390 size_t iw = j + i * p.w;
391 const auto *l = &lqc[iw];
392 F.add(Ax[iw], alphaq[iw][0]);
393 F.sub(A[l->x], alphaq[iw][0]);
394
395 F.add(Ay[iw], alphaq[iw][1]);
396 F.sub(A[l->y], alphaq[iw][1]);
397
398 F.add(Az[iw], alphaq[iw][2]);
399 F.sub(A[l->z], alphaq[iw][2]);
400 }
401 }
402 }
403
404 // layout a witness block where the "witness" is public, and
405 // thus the randomess is zero.
406 static void layout_Aext(Elt Aext[/*>=block*/], const LigeroParam<Field> &p,
407 size_t i, const Elt A[/*nwqrow, nw*/],
408 const Field &F) {
409 Blas<Field>::clear(p.r, &Aext[0], 1, F);
410 Blas<Field>::copy(p.w, &Aext[p.r], 1, &A[i * p.w], 1);
411 }
412
413 static void column_hash(size_t n, const Elt x[/*n:incx*/], size_t incx,
414 SHA256 &sha, const Field &F) {
415 for (size_t i = 0; i < n; ++i) {
416 uint8_t buf[Field::kBytes];
417 F.to_bytes_field(buf, x[i * incx]);
418 sha.Update(buf, sizeof(buf));
419 }
420 }
421};
422
423// A struct representing the hash of llterms. It is really the
424// same as Digest, but in theory Ligero should exist independently
425// of the Merkle tree.
427 static constexpr size_t kLength = kSHA256DigestSize;
428 uint8_t bytes[kLength];
429};
430
431} // namespace proofs
432
433#endif // PRIVACY_PROOFS_ZK_LIB_LIGERO_LIGERO_PARAM_H_
Definition ligero_param.h:359
Definition crypto.h:40
Definition merkle_tree.h:33
Definition gf2_128.h:63
Definition ligero_param.h:294
Definition ligero_param.h:426
Definition ligero_param.h:344
Definition ligero_param.h:117
Definition ligero_param.h:352
Definition merkle_commitment.h:36
Definition node.h:30