Longfellow ZK 0290cb32
Loading...
Searching...
No Matches
verify_circuit.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_ECDSA_VERIFY_CIRCUIT_H_
16#define PRIVACY_PROOFS_ZK_LIB_CIRCUITS_ECDSA_VERIFY_CIRCUIT_H_
17
18#include <stddef.h>
19
20#include "circuits/compiler/compiler.h"
21#include "circuits/logic/bit_plucker.h"
22
23namespace proofs {
24// Verify ECDSA signature using triple scalar mult form.
25//
26// The field used by sumcheck is the base field of the elliptic curve.
27// Compiled circuit: ecdsa verify
28// d: 7 wires: 21099 in: 1038 out:764 use:13911 ovh:7188 t:42963 cse:11351
29// notn:34724
30//
31template <class LogicCircuit, class Field, class EC>
32class VerifyCircuit {
33 using EltW = typename LogicCircuit::EltW;
34 using BitW = typename LogicCircuit::BitW;
35 using Elt = typename LogicCircuit::Elt;
36 using Nat = typename Field::N;
37 static constexpr size_t kBits = EC::kBits;
38 using Bitvec = typename LogicCircuit::v256;
39
40 public:
41 struct Witness {
42 EltW rx, ry;
43 EltW pre[8];
44 EltW rx_inv, s_inv, pk_inv;
45 EltW bi[kBits];
46 EltW int_x[kBits - 1];
47 EltW int_y[kBits - 1];
48 EltW int_z[kBits - 1];
49
50 void input(QuadCircuit<Field>& Q) {
51 rx = Q.input();
52 ry = Q.input();
53 rx_inv = Q.input();
54 s_inv = Q.input();
55 pk_inv = Q.input();
56 for (size_t i = 0; i < 8; ++i) {
57 pre[i] = Q.input();
58 }
59 for (size_t i = 0; i < kBits; ++i) {
60 bi[i] = Q.input();
61 if (i < kBits - 1) {
62 int_x[i] = Q.input();
63 int_y[i] = Q.input();
64 int_z[i] = Q.input();
65 }
66 }
67 }
68 };
69
70 VerifyCircuit(const LogicCircuit& lc, const EC& ec, const Nat& order)
71 : lc_(lc), ec_(ec), k2_(lc_.elt(2)), k3_(lc_.elt(3)) {
72 // Compute the bit representation of the order of the curve.
73 for (size_t i = 0; i < ec.kBits; ++i) {
74 bits_n_[i] = lc_.bit(order.bit(i));
75 }
76 }
77
78 // This verify takes the triple (pkx,pky,e) and checks that there exists
79 // (r=rx, ry, s) such that:
80 // identity = g*e + pk*r + (rx,ry)*-s
81 // It performs this check using a witness table that includes
82 // (g+pk, g+r, r+pk, g+r+pk), a correction element,
83 // bits of exponents (e, r, -s) s.t. each triple of bits is packed into {0,7},
84 // and intermediate ec points in (x,y,z) form. The bits are used to index the
85 // witness table in order to compute the right-hand side in a loop. The loop
86 // is sliced by providing the intermediate results in order reduce depth.
87 //
88 // An external constraint will need to ensure that e \neq 0 (e.g.,
89 // either the verifier checks this as part of the public input, or
90 // the hash that defines e is produced in the circuit). In our mdoc case,
91 // we use the later checks for both signatures.
92 //
93 // Other checks:
94 // r is interpreted as both in the base field and the scalar field.
95 // As a result, rx_inv is provided to ensure that r != 0.
96 // Similarly, s_inv is provided to ensure that s != 0.
97 //
98 // (rx,ry) is verified to be on the curve.
99 //
100 // (pkx, pky) \neq identity, because we set pk_z=1, we verify that
101 // pkx != 0, and we ensure that (pkx,pky) is on the curve.
102 //
103 void verify_signature3(EltW pk_x, EltW pk_y, EltW e, const Witness& w) const {
104 EltW zero = lc_.konst(lc_.zero());
105 EltW one = lc_.konst(lc_.one());
106 EltW gx = lc_.konst(ec_.gx_), gy = lc_.konst(ec_.gy_);
107
108 // indices for the pre[] table, don't change order
109 enum PreIndex {
110 GPK_X = 0,
111 GPK_Y,
112 GR_X,
113 GR_Y,
114 RPK_X,
115 RPK_Y,
116 GRPK_X,
117 GRPK_Y
118 };
119
120 // These variables hold the (e,r) exponents which are computed from the
121 // bits of advice (e,r,-s). They are compared with their expected values.
122 EltW est = zero, rst = zero, sst = zero;
123
124 // initialize at the 0 point, but these indices are reset on each loop
125 EltW ax = zero, ay = one, az = zero;
126
127 // =========
128 // Verify the values received in the table are correct.
129 // By verifying these values in parallel with using them, we can reduce
130 // the depth of the resulting circuit.
131 EltW cg_pkx, cg_pky, cg_pkz;
132 EltW cr_pkx, cr_pky, cr_pkz;
133 EltW cr_gx, cr_gy, cr_gz;
134 EltW cr_g_pkx, cr_g_pky, cr_g_pkz;
135 addE(cg_pkx, cg_pky, cg_pkz, gx, gy, one, pk_x, pk_y, one);
136 addE(cr_gx, cr_gy, cr_gz, w.rx, w.ry, one, gx, gy, one);
137 addE(cr_pkx, cr_pky, cr_pkz, w.rx, w.ry, one, pk_x, pk_y, one);
138 addE(cr_g_pkx, cr_g_pky, cr_g_pkz, gx, gy, one, w.pre[RPK_X], w.pre[RPK_Y],
139 one);
140 point_equality(cg_pkx, cg_pky, cg_pkz, w.pre[GPK_X], w.pre[GPK_Y]);
141 point_equality(cr_gx, cr_gy, cr_gz, w.pre[GR_X], w.pre[GR_Y]);
142 point_equality(cr_pkx, cr_pky, cr_pkz, w.pre[RPK_X], w.pre[RPK_Y]);
143 point_equality(cr_g_pkx, cr_g_pky, cr_g_pkz, w.pre[GRPK_X], w.pre[GRPK_Y]);
144
145 EltW arr_x[] = {zero, gx, pk_x, w.pre[GPK_X],
146 w.rx, w.pre[GR_X], w.pre[RPK_X], w.pre[GRPK_X]};
147 EltW arr_y[] = {one, gy, pk_y, w.pre[GPK_Y],
148 w.ry, w.pre[GR_Y], w.pre[RPK_Y], w.pre[GRPK_Y]};
149 EltW arr_z[] = {zero, one, one, one, one, one, one, one};
150 EltW arr_e[] = {zero, one, zero, one, zero, one, zero, one};
151 EltW arr_r[] = {zero, zero, one, one, zero, zero, one, one};
152 EltW arr_s[] = {zero, zero, zero, zero, one, one, one, one};
153
154 // To verify that the advice bit is in [0,7], we need to mux the point
155 // corresponding to the advice bit using degree 8 (9 points). We use the
156 // EltMuxer<LogicCircuit, 9, 8> to do this. See comments in EltMuxer.
157 EltW arr_v[] = {zero, zero, zero, zero, zero, zero, zero, zero, one};
158
159 EltMuxer<LogicCircuit, 8> xx(lc_, arr_x);
160 EltMuxer<LogicCircuit, 8> yy(lc_, arr_y);
161 EltMuxer<LogicCircuit, 8> zz(lc_, arr_z);
162 EltMuxer<LogicCircuit, 8> ee(lc_, arr_e);
163 EltMuxer<LogicCircuit, 8> rr(lc_, arr_r);
164 EltMuxer<LogicCircuit, 8> ss(lc_, arr_s);
165 EltMuxer<LogicCircuit, 9, 8> vv(lc_, arr_v);
166
167 Bitvec r_bits, s_bits;
168
169 // Traverses the bits of the scalar from high-order to low-order.
170 for (size_t i = 0; i < kBits; ++i) {
171 // Use the arr{X..V} arrays and the muxer to pick the correct point
172 // slice based on the bits of advice in the witness.
173 EltW tx = xx.mux(w.bi[i]);
174 EltW ty = yy.mux(w.bi[i]);
175 EltW tz = zz.mux(w.bi[i]);
176
177 // Update the exponent.
178 EltW e_bi = ee.mux(w.bi[i]);
179 EltW r_bi = rr.mux(w.bi[i]);
180 EltW s_bi = ss.mux(w.bi[i]);
181 auto k2 = lc_.konst(k2_);
182 est = lc_.add(&e_bi, lc_.mul(&k2, est));
183 rst = lc_.add(&r_bi, lc_.mul(&k2, rst));
184 sst = lc_.add(&s_bi, lc_.mul(&k2, sst));
185 r_bits[kBits - i - 1] = BitW(r_bi, ec_.f_);
186 s_bits[kBits - i - 1] = BitW(s_bi, ec_.f_);
187
188 // Verify that the advice bit is in [0,7].
189 EltW range = vv.mux(w.bi[i]);
190 lc_.assert0(range);
191
192 // Perform the basic add-dbl step in repeated squaring using the
193 // muxed point {tx, ty, tz}.
194 if (i > 0) {
195 doubleE(ax, ay, az, ax, ay, az);
196 }
197 addE(ax, ay, az, ax, ay, az, tx, ty, tz);
198
199 if (i < kBits - 1) {
200 // Ensure that the resulting point is equal to the intermediate
201 // point provided as input. Performing an explicit equality check
202 // ensures that all intermediate witness points are on the curve.
203 // This follows by induction. The first (ax,ay,az) is on the curve.
204 // The addition formula ensures that the i-th (ax,ay,az) is on the
205 // curve; equality ensures that the i-th witness is on the curve.
206 lc_.assert_eq(&ax, w.int_x[i]);
207 lc_.assert_eq(&ay, w.int_y[i]);
208 lc_.assert_eq(&az, w.int_z[i]);
209
210 // Use the intermediate (x,y,z) point as the next input.
211 ax = w.int_x[i];
212 ay = w.int_y[i];
213 az = w.int_z[i];
214 }
215 }
216
217 // Check that the aX,aZ points are 0.
218 lc_.assert0(ax);
219 lc_.assert0(az);
220
221 // Check that the bits used for {e,rx} correspond to the input {e, rx}.
222 lc_.assert_eq(&est, e);
223 lc_.assert_eq(&rst, w.rx);
224
225 // Check that (pk,py), (rx,ry) satisfy the curve equation.
226 is_on_curve(pk_x, pk_y);
227 is_on_curve(w.rx, w.ry);
228
229 // Verify that exponents (r,s) are not zero.
230 // A witness is provided to ensure that both values have inverses in F.
231 // A bitwise comparison is done to ensure both are < |order| of EC.
232 assert_nonzero(w.rx, w.rx_inv);
233 assert_nonzero(sst, w.s_inv);
234 assert_nonzero(pk_x, w.pk_inv);
235 auto r_range = lc_.vlt(&r_bits, bits_n_);
236 auto s_range = lc_.vlt(&s_bits, bits_n_);
237 lc_.assert1(r_range);
238 lc_.assert1(s_range);
239 }
240
241 private:
242 void assert_nonzero(EltW x, EltW witness) const {
243 auto maybe_one = lc_.mul(&x, witness);
244 auto one = lc_.konst(lc_.one());
245 lc_.assert_eq(&maybe_one, one);
246 }
247
248 void point_equality(EltW x, EltW y, EltW z, EltW p_x, EltW p_y) const {
249 lc_.assert_eq(&x, lc_.mul(&z, p_x));
250 lc_.assert_eq(&y, lc_.mul(&z, p_y));
251 }
252
253 void is_on_curve(EltW x, EltW y) const {
254 // Check that y^2 = x^3 + ax + b
255 auto yy = lc_.mul(&y, y);
256 auto xx = lc_.mul(&x, x);
257 auto xxx = lc_.mul(&x, xx);
258 auto ax = lc_.mul(ec_.a_, x);
259 auto b = lc_.konst(ec_.b_);
260 auto axb = lc_.add(&ax, b);
261 auto rhs = lc_.add(&axb, xxx);
262 lc_.assert_eq(&yy, rhs);
263 }
264
265 void addE(EltW& X3, EltW& Y3, EltW& Z3, EltW X1, EltW Y1, EltW Z1, EltW X2,
266 EltW Y2, EltW Z2) const {
267 // The general case.
268 // Algorithm 1: Complete, projective point addition for arbitrary prime
269 // order short Weierstrass curves E/Fq : y^2 = x^3 + ax + b
270 // The compiler seems to optimize the cases when Z1,Z2=1.
271 EltW t0 = lc_.mul(&X1, X2);
272 EltW t1 = lc_.mul(&Y1, Y2);
273 EltW t2 = lc_.mul(&Z1, Z2);
274 EltW t3 = lc_.add(&X1, Y1);
275 EltW t4 = lc_.add(&X2, Y2);
276 t3 = lc_.mul(&t3, t4);
277 t4 = lc_.add(&t0, t1);
278 t3 = lc_.sub(&t3, t4);
279 t4 = lc_.add(&X1, Z1);
280 EltW t5 = lc_.add(&X2, Z2);
281 t4 = lc_.mul(&t4, t5);
282 t5 = lc_.add(&t0, t2);
283 t4 = lc_.sub(&t4, t5);
284 t5 = lc_.add(&Y1, Z1);
285 EltW X3t = lc_.add(&Y2, Z2);
286 t5 = lc_.mul(&t5, X3t);
287 X3t = lc_.add(&t1, t2);
288 t5 = lc_.sub(&t5, X3t);
289 auto a = lc_.konst(ec_.a_);
290 EltW Z3t = lc_.mul(&a, t4);
291 auto k3b = lc_.konst(ec_.k3b);
292 X3t = lc_.mul(&k3b, t2);
293 Z3t = lc_.add(&X3t, Z3t);
294 X3t = lc_.sub(&t1, Z3t);
295 Z3t = lc_.add(&t1, Z3t);
296 EltW Y3t = lc_.mul(&X3t, Z3t);
297 t1 = lc_.add(&t0, t0);
298 t1 = lc_.add(&t1, t0);
299 t2 = lc_.mul(&a, t2);
300 t4 = lc_.mul(&k3b, t4);
301 t1 = lc_.add(&t1, t2);
302 t2 = lc_.sub(&t0, t2);
303 t2 = lc_.mul(&a, t2);
304 t4 = lc_.add(&t4, t2);
305 t0 = lc_.mul(&t1, t4);
306 Y3t = lc_.add(&Y3t, t0);
307 t0 = lc_.mul(&t5, t4);
308 X3t = lc_.mul(&t3, X3t);
309 X3t = lc_.sub(&X3t, t0);
310 t0 = lc_.mul(&t3, t1);
311 Z3t = lc_.mul(&t5, Z3t);
312 Z3t = lc_.add(&Z3t, t0);
313
314 X3 = X3t;
315 Y3 = Y3t;
316 Z3 = Z3t;
317 }
318
319 void doubleE(EltW& X3, EltW& Y3, EltW& Z3, EltW X, EltW Y, EltW Z) const {
320 // The general case.
321 // Algorithm 3: Exception-free point doubling for arbitrary prime order
322 // short Weierstrass curves E/Fq : y^2 = x^3 + ax + b.
323 // The compiler will presumably optimize away 0 mults when a=0 and 1
324 // mults when Z = 1.
325 EltW t0 = lc_.mul(&X, X);
326 EltW t1 = lc_.mul(&Y, Y);
327 EltW t2 = lc_.mul(&Z, Z);
328 EltW t3 = lc_.mul(&X, Y);
329 t3 = lc_.add(&t3, t3);
330 EltW Z3t = lc_.mul(&X, Z);
331 Z3t = lc_.add(&Z3t, Z3t);
332 auto a = lc_.konst(ec_.a_);
333 auto k3b = lc_.konst(ec_.k3b);
334 EltW X3t = lc_.mul(&a, Z3t);
335 EltW Y3t = lc_.mul(&k3b, t2);
336 Y3t = lc_.add(&X3t, Y3t);
337 X3t = lc_.sub(&t1, Y3t);
338 Y3t = lc_.add(&t1, Y3t);
339 Y3t = lc_.mul(&X3t, Y3t);
340 X3t = lc_.mul(&t3, X3t);
341 Z3t = lc_.mul(&k3b, Z3t);
342 t2 = lc_.mul(&a, t2);
343 t3 = lc_.sub(&t0, t2);
344 t3 = lc_.mul(&a, t3);
345 t3 = lc_.add(&t3, Z3t);
346 Z3t = lc_.add(&t0, t0);
347 t0 = lc_.add(&Z3t, t0);
348 t0 = lc_.add(&t0, t2);
349 t0 = lc_.mul(&t0, t3);
350 Y3t = lc_.add(&Y3t, t0);
351 t2 = lc_.mul(&Y, Z);
352 t2 = lc_.add(&t2, t2);
353 t0 = lc_.mul(&t2, t3);
354 X3t = lc_.sub(&X3t, t0);
355 Z3t = lc_.mul(&t2, t1);
356 Z3t = lc_.add(&Z3t, Z3t);
357 Z3t = lc_.add(&Z3t, Z3t);
358
359 X3 = X3t;
360 Y3 = Y3t;
361 Z3 = Z3t;
362 }
363
364 const LogicCircuit& lc_;
365 const EC& ec_;
366
367 Elt k2_, k3_;
368 Bitvec bits_n_;
369};
370} // namespace proofs
371
372#endif // PRIVACY_PROOFS_ZK_LIB_CIRCUITS_ECDSA_VERIFY_CIRCUIT_H_
Definition compiler.h:50
Definition verify_circuit.h:41