Longfellow ZK 0290cb32

Loading...
Searching...
No Matches
mdoc_1f.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_MDOC_MDOC_1F_H_
16#define PRIVACY_PROOFS_ZK_LIB_CIRCUITS_MDOC_MDOC_1F_H_
17
18#include <cstddef>
19#include <cstdint>
20#include <vector>
21
22#include "circuits/cbor_parser/cbor.h"
23#include "circuits/compiler/compiler.h"
24#include "circuits/ecdsa/verify_circuit.h"
25#include "circuits/logic/bit_plucker.h"
26#include "circuits/logic/counter.h"
27#include "circuits/logic/routing.h"
28#include "circuits/mdoc/mdoc_1f_io.h"
29#include "circuits/mdoc/mdoc_constants.h"
30#include "circuits/sha/flatsha256_circuit.h"
31#include "util/panic.h"
32 namespace proofs {
33
34 template <class LogicCircuit, class Field, class EC, size_t kNumAttr>
35 class mdoc_1f {
36 using EltW = typename LogicCircuit::EltW;
37 using Elt = typename LogicCircuit::Elt;
38 using Nat = typename Field::N;
39 using Ecdsa = VerifyCircuit<LogicCircuit, Field, EC>;
40 using EcdsaWitness = typename Ecdsa::Witness;
41
42 using v8 = typename LogicCircuit::v8;
43 using v32 = typename LogicCircuit::v32;
44 using v256 = typename LogicCircuit::v256;
45 using Flatsha =
46 FlatSHA256Circuit<LogicCircuit,
47 BitPlucker<LogicCircuit, kMdoc1SHAPluckerBits>>;
48 using Routing = Routing<LogicCircuit>;
49 using ShaBlockWitness = typename Flatsha::BlockWitness;
50 using sha_packed_v32 = typename Flatsha::packed_v32;
51 using Cbor = Cbor<LogicCircuit, kMdoc1CborIndexBits>;
52 using vind = typename Cbor::vindex;
53
54 const LogicCircuit& lc_;
55 const EC& ec_;
56 const Nat& order_;
57
58 public:
59 struct CborIndex {
60 vind k, v, ndx;
61 void input(const LogicCircuit& lc) {
62 k = lc.template vinput<kMdoc1CborIndexBits>();
63 v = lc.template vinput<kMdoc1CborIndexBits>();
64 ndx = lc.template vinput<kMdoc1CborIndexBits>();
65 }
66 };
67
68 struct AttrShift {
69 vind offset;
70 vind len;
71 void input(const LogicCircuit& lc) {
72 offset = lc.template vinput<kMdoc1CborIndexBits>();
73 len = lc.template vinput<kMdoc1CborIndexBits>();
74 }
75 };
76
77 class Witness {
78 public:
79 EltW e_;
80 EltW dpkx_, dpky_;
81
82 EcdsaWitness sig_;
83 EcdsaWitness dpk_sig_;
84
85 v8 in_[64 * kMdoc1MaxSHABlocks]; /* input bytes, 64 * MAX */
86 v8 nb_; /* index of sha block that contains the real hash */
87 ShaBlockWitness sig_sha_[kMdoc1MaxSHABlocks];
88
89 size_t num_attr_;
90
91 std::vector<std::vector<ShaBlockWitness>> attr_sha_;
92 std::vector<std::vector<v8>> attrb_;
93
94 std::vector<CborIndex> attr_mso_;
95 std::vector<AttrShift> attr_ei_;
96 std::vector<AttrShift> attr_ev_;
97
98 std::vector<v8> incb_;
99 std::vector<typename Cbor::position_witness> pwcb_;
100 typename Cbor::global_witness gwcb_;
101
102 vind prepad_, mso_len_;
103
104 CborIndex valid_, valid_from_, valid_until_;
105 CborIndex dev_key_info_, dev_key_, dev_key_pkx_, dev_key_pky_;
106 CborIndex value_digests_, org_;
107
108 explicit Witness(size_t num_attr)
109 : num_attr_(num_attr),
110 attr_sha_(num_attr),
111 attrb_(num_attr),
112 attr_mso_(num_attr),
113 attr_ei_(num_attr),
114 attr_ev_(num_attr),
115 incb_(kMdoc1MaxMsoLen),
116 pwcb_(kMdoc1MaxMsoLen) {
117 for (size_t i = 0; i < num_attr; ++i) {
118 attr_sha_[i].resize(2);
119 }
120 }
121
122 void input(QuadCircuit<Field>& Q, const LogicCircuit& lc) {
123 const Counter<LogicCircuit> CTRC(lc);
124
125 e_ = Q.input();
126 dpkx_ = Q.input();
127 dpky_ = Q.input();
128
129 sig_.input(Q);
130 dpk_sig_.input(Q);
131
132 nb_ = lc.template vinput<8>();
133
134 // sha input init (skip the prefix) =========================
135 for (size_t i = 0; i + kCose1PrefixLen < 64 * kMdoc1MaxSHABlocks; ++i) {
136 in_[i] = lc.template vinput<8>();
137 }
138
139 for (size_t j = 0; j < kMdoc1MaxSHABlocks; j++) {
140 sig_sha_[j].input(Q);
141 }
142
143 // Cbor input init: note, the inC array will be constructed in the
144 // circuit.
145 prepad_ = lc.template vinput<kMdoc1CborIndexBits>();
146 mso_len_ = lc.template vinput<kMdoc1CborIndexBits>();
147 for (size_t i = 0; i < kMdoc1MaxMsoLen; ++i) {
148 pwcb_[i].encoded_sel_header = Q.input();
149 }
150 gwcb_.invprod_decode = Q.input();
151 gwcb_.cc0_counter = CTRC.input();
152 gwcb_.invprod_parse = Q.input();
153
154 valid_.input(lc);
155 valid_from_.input(lc);
156 valid_until_.input(lc);
157 dev_key_info_.input(lc);
158 dev_key_.input(lc);
159 dev_key_pkx_.input(lc);
160 dev_key_pky_.input(lc);
161 value_digests_.input(lc);
162 org_.input(lc);
163
164 // Attribute opening witnesses
165 for (size_t ai = 0; ai < num_attr_; ++ai) {
166 for (size_t i = 0; i < 64 * 2; ++i) {
167 attrb_[ai].push_back(lc.template vinput<8>());
168 }
169 for (size_t j = 0; j < 2; j++) {
170 attr_sha_[ai][j].input(Q);
171 }
172 attr_mso_[ai].input(lc);
173 attr_ei_[ai].input(lc);
174 attr_ev_[ai].input(lc);
175 }
176 }
177 };
178
179 struct OpenedAttribute {
180 v8 attr[96]; // representing attribute name, elementValue delimiter, and
181 // finally the attribute value.
182 v8 len;
183 void input(const LogicCircuit& lc) {
184 for (size_t j = 0; j < 96; ++j) {
185 attr[j] = lc.template vinput<8>();
186 }
187 len = lc.template vinput<8>();
188 }
189 };
190
191 struct PathEntry {
192 CborIndex ind;
193 size_t l;
194 const uint8_t* name;
195 };
196
197 explicit mdoc_1f(const LogicCircuit& lc, const EC& ec, const Nat& order)
198 : lc_(lc), ec_(ec), order_(order), sha_(lc), r_(lc), cbor_(lc) {}
199
200 void assert_credential(EltW pkX, EltW pkY, EltW hash_tr,
201 OpenedAttribute oa[/* NUM_ATTR */],
202 const v8 now[/*kDateLen*/], const Witness& vw) const {
203 Ecdsa ecc(lc_, ec_, order_);
204
205 ecc.verify_signature3(pkX, pkY, vw.e_, vw.sig_);
206 ecc.verify_signature3(vw.dpkx_, vw.dpky_, hash_tr, vw.dpk_sig_);
207
208 sha_.assert_message_with_prefix(kMdoc1MaxSHABlocks, vw.nb_, vw.in_,
209 kCose1Prefix, kCose1PrefixLen, vw.sig_sha_);
210 // Verify that the hash of the mdoc is equal to e.
211 assert_hash(vw.e_, vw);
212
213 // Shift a portion of the MSO into buf and check it.
214 const v8 zz = lc_.template vbit<8>(0); // cannot appear in strings
215 std::vector<v8> cmp_buf(kMdoc1MaxMsoLen);
216
217 // Re-arrange the input wires to produce the <0 padded><mso> input
218 // required for cbor parsing. The subtracted 5 corresponds to the fix
219 // length D8 18 <len2> prefix of the mso that we want to skip parsing.
220 // The subtracted 2 corresponds to the length.
221 std::vector<v8> in_cb(kMdoc1MaxMsoLen);
222 r_.unshift(vw.prepad_, kMdoc1MaxMsoLen, in_cb.data(),
223 kMdoc1MaxMsoLen - 5 - 2, vw.in_ + 5 + 2, zz, 3);
224
225 std::vector<typename Cbor::decode> dsC(kMdoc1MaxMsoLen);
226 std::vector<typename Cbor::parse_output> psC(kMdoc1MaxMsoLen);
227 cbor_.decode_and_assert_decode_and_parse(kMdoc1MaxMsoLen, dsC.data(),
228 psC.data(), in_cb.data(),
229 vw.pwcb_.data(), vw.gwcb_);
230
231 cbor_.assert_input_starts_at(kMdoc1MaxMsoLen, vw.prepad_, vw.mso_len_,
232 dsC.data());
233
234 // Validity
235 PathEntry vk[2] = {{vw.valid_, kValidityInfoLen, kValidityInfoID},
236 {vw.valid_from_, kValidFromLen, kValidFromID}};
237 assert_path(2, vk, vw, dsC, psC);
238 cbor_.assert_date_before_at(kMdoc1MaxMsoLen, vw.valid_from_.v, now,
239 dsC.data());
240
241 // validUntil is a key in validityInfo.
242 cbor_.assert_map_entry(kMdoc1MaxMsoLen, vw.valid_.v, 1, vw.valid_until_.k,
243 vw.valid_until_.v, vw.valid_until_.ndx, dsC.data(),
244 psC.data());
245 cbor_.assert_text_at(kMdoc1MaxMsoLen, vw.valid_until_.k, kValidUntilLen,
246 kValidUntilID, dsC.data());
247 cbor_.assert_date_after_at(kMdoc1MaxMsoLen, vw.valid_until_.v, now,
248 dsC.data());
249
250 PathEntry dk[2] = {{vw.dev_key_info_, kDeviceKeyInfoLen, kDeviceKeyInfoID},
251 {vw.dev_key_, kDeviceKeyLen, kDeviceKeyID}};
252 assert_path(2, dk, vw, dsC, psC);
253 cbor_.assert_map_entry(kMdoc1MaxMsoLen, vw.dev_key_.v, 2, vw.dev_key_pkx_.k,
254 vw.dev_key_pkx_.v, vw.dev_key_pkx_.ndx, dsC.data(),
255 psC.data());
256 cbor_.assert_map_entry(kMdoc1MaxMsoLen, vw.dev_key_.v, 2, vw.dev_key_pky_.k,
257 vw.dev_key_pky_.v, vw.dev_key_pky_.ndx, dsC.data(),
258 psC.data());
259 cbor_.assert_negative_at(kMdoc1MaxMsoLen, vw.dev_key_pkx_.k, 1, dsC.data());
260 cbor_.assert_negative_at(kMdoc1MaxMsoLen, vw.dev_key_pky_.k, 2, dsC.data());
261 assert_elt_as_be_bytes_at(kMdoc1MaxMsoLen, vw.dev_key_pkx_.v, 32, vw.dpkx_,
262 dsC.data());
263 assert_elt_as_be_bytes_at(kMdoc1MaxMsoLen, vw.dev_key_pky_.v, 32, vw.dpky_,
264 dsC.data());
265 // Attributes parsing
266 PathEntry ak[2] = {{vw.value_digests_, kValueDigestsLen, kValueDigestsID},
267 {vw.org_, kOrgLen, kOrgID}};
268 assert_path(2, ak, vw, dsC, psC);
269
270 // Attributes: Equality of hash with MSO value
271 for (size_t ai = 0; ai < vw.num_attr_; ++ai) {
272 auto two = lc_.template vbit<8>(2);
273 v8 B[96];
274 sha_.assert_message(2, two, vw.attrb_[ai].data(),
275 vw.attr_sha_[ai].data());
276
277 // Check the hash matches the value in the signed MSO.
278 cbor_.assert_map_entry(kMdoc1MaxMsoLen, vw.org_.v, 2, vw.attr_mso_[ai].k,
279 vw.attr_mso_[ai].v, vw.attr_mso_[ai].ndx,
280 dsC.data(), psC.data());
281 EltW h = repack32(vw.attr_sha_[ai][1].h1);
282 assert_elt_as_be_bytes_at(kMdoc1MaxMsoLen, vw.attr_mso_[ai].v, 32, h,
283 dsC.data());
284
285 // Check that the attribute_id and value occur in the hashed text.
286 r_.shift(vw.attr_ei_[ai].offset, 96, B, 128, vw.attrb_[ai].data(), zz, 3);
287 assert_attribute(96, oa[ai].len, B, oa[ai].attr);
288 }
289 }
290
291 private:
292 // TODO [matteof 2025-08-01] packing a SHA256 hash into an
293 // EltW loses some soundness, and there is no reason to do it.
294 // Get rid of repack32() and compare the individual bits/bytes.
295 EltW repack32(const sha_packed_v32 H[]) const {
296 EltW h = lc_.konst(0);
297 Elt twok = lc_.one();
298 for (size_t j = 8; j-- > 0;) {
299 auto hj = sha_.bp_.unpack_v32(H[j]);
300 for (size_t k = 0; k < 32; ++k) {
301 h = lc_.axpy(&h, twok, lc_.eval(hj[k]));
302 lc_.f_.add(twok, twok);
303 }
304 }
305 return h;
306 }
307
308 // Assert that the hash of the mdoc is equal to e.
309 // The hash is encoded in the SHA witness, and thus the correct block
310 // must be muxed for the comparison. Thus method first muxes the "packed"
311 // encoding of the SHA witness, then unpacks it and compares it to e to
312 // save a lot of work in the bit plucker.
313 void assert_hash(const EltW& e, const Witness& vw) const {
314 sha_packed_v32 x[8];
315 for (size_t b = 0; b < kMdoc1MaxSHABlocks; ++b) {
316 auto bt = lc_.veq(vw.nb_, b + 1); /* b is zero-indexed */
317 auto ebt = lc_.eval(bt);
318 for (size_t i = 0; i < 8; ++i) {
319 for (size_t k = 0; k < sha_.bp_.kNv32Elts; ++k) {
320 if (b == 0) {
321 x[i][k] = lc_.mul(&ebt, vw.sig_sha_[b].h1[i][k]);
322 } else {
323 auto maybe_sha = lc_.mul(&ebt, vw.sig_sha_[b].h1[i][k]);
324 x[i][k] = lc_.add(&x[i][k], maybe_sha);
325 }
326 }
327 }
328 }
329
330 EltW h = repack32(x);
331 lc_.assert_eq(&h, e);
332 }
333
334 // Checks that an attribute id or attribute value is as expected.
335 // The len parameter holds the byte length of the expected id or value.
336 void assert_attribute(size_t max, const v8& len, const v8 got[/*max*/],
337 const v8 want[/*max*/]) const {
338 // auto two = lc_.konst(2);
339 for (size_t j = 0; j < max; ++j) {
340 auto ll = lc_.vlt(j, len);
341 auto same = lc_.eq(8, got[j].data(), want[j].data());
342 lc_.assert_implies(&ll, same);
343 }
344 }
345
346 void assert_path(size_t len, PathEntry p[], const Witness& vw,
347 std::vector<typename Cbor::decode>& dsC,
348 std::vector<typename Cbor::parse_output>& psC) const {
349 vind start = vw.prepad_;
350 for (size_t i = 0; i < len; ++i) {
351 cbor_.assert_map_entry(kMdoc1MaxMsoLen, start, i, p[i].ind.k, p[i].ind.v,
352 p[i].ind.ndx, dsC.data(), psC.data());
353 cbor_.assert_text_at(kMdoc1MaxMsoLen, p[i].ind.k, p[i].l, p[i].name,
354 dsC.data());
355 start = p[i].ind.v;
356 }
357 }
358
359 void assert_elt_as_be_bytes_at(size_t n, const vind& j, size_t len, EltW X,
360 const typename Cbor::decode ds[/*n*/]) const {
361 const LogicCircuit& LC = lc_; // shorthand
362
363 std::vector<EltW> A(n);
364 for (size_t i = 0; i < n; ++i) {
365 A[i] = ds[i].bd.as_scalar;
366 }
367 EltW tx = LC.konst(0), k256 = LC.konst(256);
368
369 std::vector<EltW> B(2 + len);
370 size_t unroll = 3;
371 size_t si = 1;
372 r_.shift(j, len + 2, B.data(), n, A.data(), LC.konst(0), unroll);
373 if (len < 24) {
374 size_t expected_header = (2 << 5) + len;
375 auto eh = LC.konst(expected_header);
376 LC.assert_eq(&B[0], eh);
377 } else if (len < 256) {
378 size_t expected_header = (2 << 5) + 24;
379 auto eh = LC.konst(expected_header);
380 LC.assert_eq(&B[0], eh);
381 LC.assert_eq(&B[1], LC.konst(len));
382 si = 2;
383 } else {
384 check(false, "len >= 256");
385 }
386
387 for (size_t i = 0; i < len; ++i) {
388 auto tmp = LC.mul(&tx, k256);
389 tx = LC.add(&tmp, B[i + si]);
390 }
391
392 LC.assert_eq(&tx, X);
393 }
394
395 Flatsha sha_;
396 Routing r_;
397 Cbor cbor_;
398};
399
400} // namespace proofs
401
402#endif // PRIVACY_PROOFS_ZK_LIB_CIRCUITS_MDOC_MDOC_1F_H_
proofs::BitPlucker
Definition bit_plucker.h:86
proofs::FlatSHA256Circuit
Definition flatsha256_circuit.h:52
proofs::QuadCircuit
Definition compiler.h:50
proofs::VerifyCircuit
Definition verify_circuit.h:32
proofs::mdoc_1f::Witness
Definition mdoc_1f.h:77
proofs::OpenedAttribute
Definition jwt_witness.h:37
proofs::VerifyCircuit::Witness
Definition verify_circuit.h:41
proofs::mdoc_1f::AttrShift
Definition mdoc_1f.h:68
proofs::mdoc_1f::CborIndex
Definition mdoc_1f.h:59
proofs::mdoc_1f::OpenedAttribute
Definition mdoc_1f.h:179
proofs::mdoc_1f::PathEntry
Definition mdoc_1f.h:191