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