Longfellow ZK 0290cb32

Loading...
Searching...
No Matches
mdoc_witness.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_WITNESS_H_
16#define PRIVACY_PROOFS_ZK_LIB_CIRCUITS_MDOC_MDOC_WITNESS_H_
17
18#include <stddef.h>
19#include <string.h>
20
21#include <array>
22#include <cstdint>
23#include <iterator>
24#include <vector>
25
26#include "arrays/dense.h"
27#include "cbor/host_decoder.h"
28#include "circuits/ecdsa/verify_witness.h"
29#include "circuits/logic/bit_plucker_encoder.h"
30#include "circuits/mac/mac_witness.h"
31#include "circuits/mdoc/mdoc_attribute_ids.h"
32#include "circuits/mdoc/mdoc_constants.h"
33#include "circuits/mdoc/mdoc_hash.h"
34#include "circuits/mdoc/mdoc_zk.h"
35#include "circuits/sha/flatsha256_witness.h"
36#include "gf2k/gf2_128.h"
37#include "util/crypto.h"
38#include "util/log.h"
39#include "util/panic.h"
40
41 namespace proofs {
42
43 struct CborIndex {
44 size_t k, v, ndx;
45 size_t pos, len; /* optional fields for string/byte values */
46};
47
48 struct AttrShift {
49 size_t offset, len;
50};
51
52// This class represents an attribute that is parsed out of the deviceResponse
53// data structure. It includes offsets into the original mdoc which can be used
54// to construct SHA witnesses for disclosing the value of an attribute.
55 struct FullAttribute {
56 // Offset and length of the attribute identifier and attribute value.
57 size_t id_ind;
58 size_t id_len;
59 size_t val_ind;
60 size_t val_len;
61
62 const uint8_t* mdl_ns; // mdl namespace for the attribute
63
64 // Index for this attribute among all attributes in the mdoc hash list.
65 size_t digest_id;
66 CborIndex mso;
67
68 // Offset and length of the attribute tag.
69 size_t tag_ind;
70 size_t tag_len;
71
72 // The original mdoc into which all offsets point.
73 const uint8_t* doc;
74
75 bool operator==(const RequestedAttribute& y) const {
76 return y.id_len == id_len && memcmp(y.id, &doc[id_ind], id_len) == 0;
77 }
78
79 size_t witness_length(const RequestedAttribute& attr) {
80 return id_len + val_len + 1 + 12;
81 }
82};
83
84 class ParsedMdoc {
85 public:
86 // Various cbor indices/witnesses for intermediate structures.
87 CborIndex t_mso_, sig_, dksig_;
88 CborIndex valid_, valid_from_, valid_until_;
89 CborIndex dev_key_info_, dev_key_, dev_key_pkx_, dev_key_pky_;
90 CborIndex value_digests_, org_;
91 std::vector<FullAttribute> attributes_;
92 std::vector<uint8_t> doc_type_;
93
94 // These are the exact bytes which produce the hash that is signed.
95 std::vector<uint8_t> tagged_mso_bytes_;
96
97 /*
98 Parses a byte representation of the "DeviceResponse" string from a phone.
99 This contains all of the information needed to respond to an mdoc verifier.
100 This method attempts to construct a witness from this.
101
102 8.3.2.1.2.2: first field is "version", 2nd optional field is "documents"
103 [documents][0][issuerSigned][issuerAuth]{2} --> tagged mso
104 [documents][0][issuerSigned][issuerAuth]{3} --> issuer sig
105 [documents][0][issuerSigned][nameSpaces][ns][index-of-attr] --> enc attr
106 [documents][0][deviceSigned][deviceAuth][deviceSignature][3] --> sig
107
108 This method produces indices into doc as state.
109 */
110 bool parse_device_response(size_t len, const uint8_t resp[/* len */]) {
111 size_t np = 0;
112 // When this object falls out of scope, all parsing objects will be
113 // garbage collected.
114 CborDoc root;
115 bool ok = root.decode(resp, len, np, 0);
116 if (!ok) {
117 log(ERROR, "Failed to decode root");
118 return false;
119 }
120
121 size_t di;
122 auto docs = root.lookup(resp, 9, (uint8_t*)"documents", di);
123 if (docs == nullptr) return false;
124 // Fields of Document are "docType", "issuerSigned", "deviceSigned", ?errors
125
126 auto docs0 = docs[1].index(0);
127 if (docs0 == nullptr) return false;
128
129 auto dt = docs0[0].lookup(resp, 7, (uint8_t*)"docType", di);
130 if (dt == nullptr) return false;
131 doc_type_.insert(doc_type_.begin(), resp + dt[1].u_.string.pos,
132 resp + dt[1].u_.string.pos + dt[1].u_.string.len);
133
134 // The returned docs0 is the map, so index at [0].
135 auto is = docs0[0].lookup(resp, 12, (uint8_t*)"issuerSigned", di);
136 if (is == nullptr) return false;
137
138 auto ia = is[1].lookup(resp, 10, (uint8_t*)"issuerAuth", di);
139 if (ia == nullptr) return false;
140
141 auto tmso = ia[1].index(2);
142 if (tmso == nullptr) return false;
143 copy_header(t_mso_, tmso);
144 auto nsig = ia[1].index(3);
145 if (nsig == nullptr) return false;
146 copy_header(sig_, nsig);
147
148 auto ns = is[1].lookup(resp, 10, (uint8_t*)"nameSpaces", di);
149 if (ns == nullptr) return false;
150
151 // Find the attribute witness we need from here.
152 for (const char* sn : kSupportedNamespaces) {
153 auto mldns = ns[1].lookup(resp, strlen(sn), (const uint8_t*)sn, di);
154 if (mldns == nullptr) continue;
155 size_t ai = 0;
156 auto tattr = mldns[1].index(ai++);
157 while (tattr != nullptr) {
158 CborDoc er;
159 // Decode the map in this tagged attribute.
160 size_t pos = tattr->children_[0].u_.string.pos;
161 size_t end = pos + tattr->children_[0].u_.string.len;
162 if (!er.decode(resp, end, pos, 0)) {
163 return false;
164 }
165
166 auto ei = er.lookup(resp, 17, (uint8_t*)"elementIdentifier", di);
167 if (ei == nullptr) return false;
168 auto ev = er.lookup(resp, 12, (uint8_t*)"elementValue", di);
169 if (ev == nullptr) return false;
170 auto digid = er.lookup(resp, 8, (uint8_t*)"digestID", di);
171 if (digid == nullptr) return false;
172
173 attributes_.push_back((FullAttribute){
174 ei[1].position(),
175 ei[1].length(),
176 ev[1].position(),
177 ev[1].length(),
178 (const uint8_t*)sn,
179 static_cast<size_t>(digid[1].u_.u64), /* digest_id */
180 {0, 0, 0}, /* default mso_ind */
181 tattr->header_pos_, /* tag_ind */
182 tattr->children_[0].u_.string.len +
183 4, /* +4 for the D8 18 58 <> prefix */
184 resp});
185
186 tattr = mldns[1].index(ai++);
187 }
188 }
189
190 auto ds = docs0[0].lookup(resp, 12, (uint8_t*)"deviceSigned", di);
191 if (ds == nullptr) return false;
192 auto da = ds[1].lookup(resp, 10, (uint8_t*)"deviceAuth", di);
193 if (da == nullptr) return false;
194 auto dsi = da[1].lookup(resp, 15, (uint8_t*)"deviceSignature", di);
195 if (dsi == nullptr) return false;
196 auto ndksig = dsi[1].index(3);
197 if (ndksig == nullptr) return false;
198 copy_header(dksig_, ndksig);
199
200 // Then parse tagged mso. Skip 5 bytes to skip the D8 18 59 <len2>.
201 const uint8_t* pmso = resp + tmso->u_.string.pos + 5;
202 size_t pos = 0;
203 CborDoc mso;
204 if (!mso.decode(pmso, tmso->u_.string.len - 5, pos, 0)) return false;
205 auto nv = mso.lookup(pmso, kValidityInfoLen, kValidityInfoID, valid_.ndx);
206 if (nv == nullptr) return false;
207 copy_kv_header(valid_, nv);
208
209 auto nvf = nv[1].lookup(pmso, kValidFromLen, kValidFromID, valid_from_.ndx);
210 if (nvf == nullptr) return false;
211 copy_kv_header(valid_from_, nvf);
212
213 auto nvu =
214 nv[1].lookup(pmso, kValidUntilLen, kValidUntilID, valid_until_.ndx);
215 if (nvu == nullptr) return false;
216 copy_kv_header(valid_until_, nvu);
217
218 auto ndki = mso.lookup(pmso, kDeviceKeyInfoLen, kDeviceKeyInfoID,
219 dev_key_info_.ndx);
220 if (ndki == nullptr) return false;
221 copy_kv_header(dev_key_info_, ndki);
222
223 auto ndk = ndki[1].lookup(pmso, kDeviceKeyLen, kDeviceKeyID, dev_key_.ndx);
224 if (ndk == nullptr) return false;
225 copy_kv_header(dev_key_, ndk);
226
227 auto npkx = ndk[1].lookup_negative(-1, dev_key_pkx_.ndx);
228 if (npkx == nullptr) return false;
229 copy_kv_header(dev_key_pkx_, npkx);
230
231 auto npky = ndk[1].lookup_negative(-2, dev_key_pky_.ndx);
232 if (npky == nullptr) return false;
233 copy_kv_header(dev_key_pky_, npky);
234
235 auto nvd =
236 mso.lookup(pmso, kValueDigestsLen, kValueDigestsID, value_digests_.ndx);
237 if (nvd == nullptr) return false;
238 copy_kv_header(value_digests_, nvd);
239
240 // For backwards compatibility with 1f circuits, copy the hard-coded org_ if
241 // it is present. TODO(shelat): Remove this once all 1f circuits have
242 // been updated.
243 auto norg = nvd[1].lookup(pmso, kOrgLen, kOrgID, org_.ndx);
244 if (norg != nullptr) {
245 copy_kv_header(org_, norg);
246 }
247
248 for (auto& attr : attributes_) {
249 size_t index;
250 auto nss = nvd[1].lookup(pmso, strlen((const char*)attr.mdl_ns),
251 attr.mdl_ns, index);
252 if (nss == nullptr) return false;
253 uint64_t hi = (uint64_t)attr.digest_id;
254 auto hattr = nss[1].lookup_unsigned(hi, attr.mso.ndx);
255 if (hattr == nullptr) return false;
256 copy_kv_header(attr.mso, hattr);
257 }
258
259 tagged_mso_bytes_.assign(std::begin(kCose1Prefix), std::end(kCose1Prefix));
260 // Add 2-byte length
261 tagged_mso_bytes_.push_back((t_mso_.len >> 8) & 0xff);
262 tagged_mso_bytes_.push_back(t_mso_.len & 0xff);
263 for (size_t i = 0; i < t_mso_.len; ++i) {
264 tagged_mso_bytes_.push_back(resp[t_mso_.pos + i]);
265 }
266
267 return true;
268 }
269
270 private:
271 // Used to copy the results of a map lookup.
272 static void copy_kv_header(CborIndex& ind, const CborDoc* n) {
273 ind.k = n[0].header_pos_;
274 ind.v = n[1].header_pos_;
275
276 if (n[1].t_ == TEXT || n[1].t_ == BYTES) {
277 ind.pos = n[1].u_.string.pos;
278 ind.len = n[1].u_.string.len;
279 }
280 }
281
282 // Used to copy the results of an index lookup.
283 static void copy_header(CborIndex& ind, const CborDoc* n) {
284 ind.k = n->header_pos_;
285 ind.pos = n->u_.string.pos;
286 ind.len = n->u_.string.len;
287 }
288};
289
290// Transform from u8 be (i.e., be[31] is the most significant byte) into
291// nat form, which requires first converting to le byte order.
292 template <class Nat>
293 Nat nat_from_be(const uint8_t be[/* Nat::kBytes */]) {
294 uint8_t tmp[Nat::kBytes];
295 // Transform into byte-wise le representation.
296 for (size_t i = 0; i < Nat::kBytes; ++i) {
297 tmp[i] = be[Nat::kBytes - i - 1];
298 }
299 return Nat::of_bytes(tmp);
300}
301
302// Transform from u32 be (i.e., be[0] is the most significant nibble)
303// into nat form, which requires first converting to le byte order.
304 template <class Nat>
305 Nat nat_from_u32(const uint32_t be[]) {
306 uint8_t tmp[Nat::kBytes];
307 const size_t top = Nat::kBytes / 4;
308 for (size_t i = 0; i < Nat::kBytes; ++i) {
309 tmp[i] = (be[top - i / 4 - 1] >> ((i % 4) * 8)) & 0xff;
310 }
311 return Nat::of_bytes(tmp);
312}
313
314 template <typename Nat>
315 Nat nat_from_hash(const uint8_t data[], size_t len) {
316 uint8_t hash[kSHA256DigestSize];
317 SHA256 sha;
318 sha.Update(data, len);
319 sha.DigestData(hash);
320 Nat ne = nat_from_be<Nat>(hash);
321 return ne;
322}
323
324// Append the cbor encoding of the length of a bytestring to buf.
325// This method handles bytestrings that are up to 255 bytes long.
326 static inline void append_bytes_len(std::vector<uint8_t>& buf, size_t len) {
327 check(len < 65536, "Bytestring length too large");
328 if (len < 24) {
329 buf.push_back(0x40 + len);
330 } else if (len < 256) {
331 uint8_t ll[] = {0x58, static_cast<uint8_t>(len & 0xff)};
332 buf.insert(buf.end(), ll, ll + 2);
333 } else {
334 uint8_t ll[] = {0x59, (uint8_t)((len >> 8) & 0xff), (uint8_t)(len & 0xff)};
335 buf.insert(buf.end(), ll, ll + 3);
336 }
337}
338
339// Append the cbor encoding of the length of a text string to buf.
340// This method handles text strings that are up to 255 bytes long.
341 static inline void append_text_len(std::vector<uint8_t>& buf, size_t len) {
342 check(len < 256, "Text length too large");
343 if (len < 24) {
344 buf.push_back(0x60 + len);
345 } else if (len < 256) {
346 buf.push_back(0x78);
347 buf.push_back(len);
348 }
349}
350
351// Form the COSE1 encoding of the DeviceAuthenticationBytes,
352// then compute its SHA-256 hash, and cast into a Nat.
353// The original form follows S9.1.3.4 of the mdoc spec and
354// assumed that the transcript was a simple string.
355// The Jan'2024 demo requires using the "AndroidHandover" version
356// of the DeviceAuthenticationBytes formatting, which is not
357// specified in the spec. As a result, this function is a hack
358// that mimics the bytes produced by the Android com.android.identity.wallet
359// library.
360 template <class Nat>
361 static Nat compute_transcript_hash(
362 const uint8_t transcript[], size_t len,
363 const std::vector<uint8_t>* docType = nullptr) {
364 // The DeviceAuthenticationBytes is defined in 9.1.3.4 as:
365 // DeviceAuthentication = [
366 // "DeviceAuthentication",
367 // SessionTranscript,
368 // DocType, ; Same as in mdoc response
369 // DeviceNameSpacesBytes ; Same as in mdoc response
370 // ]
371 std::vector<uint8_t> deviceAuthentication = {
372 0x84, 0x74, 'D', 'e', 'v', 'i', 'c', 'e', 'A', 'u', 't',
373 'h', 'e', 'n', 't', 'i', 'c', 'a', 't', 'i', 'o', 'n',
374 };
375 std::vector<uint8_t> docTypeBytes = {
376 0x75, 'o', 'r', 'g', '.', 'i', 's', 'o', '.', '1', '8',
377 '0', '1', '3', '.', '5', '.', '1', '.', 'm', 'D', 'L',
378 };
379 std::vector<uint8_t> deviceNameSpacesBytes = {0xD8, 0x18, 0x41, 0xA0};
380
381 if (docType != nullptr && docType->size() < 256) {
382 docTypeBytes.clear();
383 append_text_len(docTypeBytes, docType->size());
384 docTypeBytes.insert(docTypeBytes.end(), docType->begin(), docType->end());
385 }
386
387 // Provide the DeviceAuthentication bytes
388 std::vector<uint8_t> da(deviceAuthentication);
389 da.insert(da.end(), transcript, transcript + len);
390 da.insert(da.end(), docTypeBytes.begin(), docTypeBytes.end());
391 da.insert(da.end(), deviceNameSpacesBytes.begin(),
392 deviceNameSpacesBytes.end());
393
394 // Form the COSE1 encoding of the DeviceAuthenticationBytes.
395 std::vector<uint8_t> cose1{0x84, 0x6A, 0x53, 0x69, 0x67, 0x6E,
396 0x61, 0x74, 0x75, 0x72, 0x65, 0x31,
397 0x43, 0xA1, 0x01, 0x26, 0x40};
398 uint8_t tag[] = {0xD8, 0x18};
399
400 size_t l1 = da.size();
401 size_t l2 = l1 + (l1 < 256 ? 4 : 5); /* Tagged array length. */
402 append_bytes_len(cose1, l2);
403 cose1.insert(cose1.end(), tag, tag + 2);
404 append_bytes_len(cose1, l1);
405 cose1.insert(cose1.end(), da.begin(), da.end());
406
407 return nat_from_hash<Nat>(cose1.data(), cose1.size());
408}
409
410// Interpret input s as an len*8-bit string, and use it to fill max*8 bits
411// in the dense filler.
412// Pad the input with the Field value 2 to indicate the positions
413// that are not part of the string.
414 template <class Field>
415 void fill_bit_string(DenseFiller<Field>& filler, const uint8_t s[/*len*/],
416 size_t len, size_t max, const Field& Fs) {
417 std::vector<typename Field::Elt> v(max * 8, Fs.of_scalar(2));
418 for (size_t i = 0; i < max && i < len; ++i) {
419 fill_byte(v, s[i], i, Fs);
420 }
421 filler.push_back(v);
422}
423
424 template <class Field>
425 void fill_byte(std::vector<typename Field::Elt>& v, uint8_t b, size_t i,
426 const Field& F) {
427 for (size_t j = 0; j < 8; ++j) {
428 v[i * 8 + j] = (b >> j & 0x1) ? F.one() : F.zero();
429 }
430}
431
432 template <class Field>
433 bool fill_attribute(DenseFiller<Field>& filler, const RequestedAttribute& attr,
434 const Field& F, size_t version) {
435 // In version 3, the attribute is encoded as the raw cbor string that
436 // included <name of identifier> <elementValue> <attributeValue>.
437 // In version 4, the attribute is encoded as
438 // <len(identifier)> <name of identifier> <elementValue> <attributeValue>.
439 // This extra length field distinguishes the two attributes:
440 // "aamva/domestic_driving_privileges" from "iso/driving_privileges." No
441 // other valid attribute name is a proper suffix of another. See the
442 // mdoc_attribute_ids.h file for the full list of attribute names and our
443 // restrictions.
444
445 std::vector<uint8_t> vbuf;
446 std::vector<typename Field::Elt> v(96 * 8, F.zero());
447
448 if (version >= 4) {
449 // Append the length of the elementIdentifier.
450 append_text_len(vbuf, attr.id_len);
451 }
452 vbuf.insert(vbuf.end(), attr.id, attr.id + attr.id_len);
453 append_text_len(vbuf, 12); // len of "elementValue"
454 const char* ev = "elementValue";
455 vbuf.insert(vbuf.end(), ev, ev + 12);
456
457 vbuf.insert(vbuf.end(), attr.cbor_value,
458 attr.cbor_value + attr.cbor_value_len);
459
460 if (vbuf.size() > 96) {
461 log(ERROR, "Attribute %s is too long: %zu", attr.id, vbuf.size());
462 return false;
463 }
464 size_t len = 0;
465 for (size_t j = 0; j < vbuf.size() && len < 96; ++j, ++len) {
466 fill_byte(v, vbuf[j], len, F);
467 }
468 filler.push_back(v);
469 if (version >= 4) {
470 // In version 4, add the OpenedAttribute.len field.
471 filler.push_back(len, 8, F);
472 }
473 return true;
474}
475
476 template <class EC, class ScalarField>
477 class MdocSignatureWitness {
478 using Field = typename EC::Field;
479 using Elt = typename Field::Elt;
480 using Nat = typename Field::N;
481 using EcdsaWitness = VerifyWitness3<EC, ScalarField>;
482 using MacWitnessF = MacWitness<Field>;
483 using f_128 = GF2_128<>;
484 const EC& ec_;
485 const f_128& gf_;
486
487 public:
488 Elt e_, e2_; /* Issuer signature values. */
489 Elt dpkx_, dpky_; /* device key */
490 EcdsaWitness ew_, dkw_;
491 MacWitnessF macs_[3]; /* macs for e_, dpkx_, dpky_ */
492
493 explicit MdocSignatureWitness(const EC& ec, const ScalarField& Fn,
494 const f_128& gf)
495 : ec_(ec),
496 gf_(gf),
497 ew_(Fn, ec),
498 dkw_(Fn, ec),
499 macs_{MacWitnessF(ec.f_, gf_), MacWitnessF(ec.f_, gf_),
500 MacWitnessF(ec.f_, gf_)} {}
501
502 void fill_witness(DenseFiller<Field>& filler) const {
503 filler.push_back(e_);
504 filler.push_back(dpkx_);
505 filler.push_back(dpky_);
506
507 ew_.fill_witness(filler);
508 dkw_.fill_witness(filler);
509 for (auto& mac : macs_) {
510 mac.fill_witness(filler);
511 }
512 }
513
514 bool compute_witness(Elt pkX, Elt pkY, const uint8_t mdoc[/* len */],
515 size_t len, const uint8_t transcript[/* tlen */],
516 size_t tlen) {
517 ParsedMdoc pm;
518
519 if (!pm.parse_device_response(len, mdoc)) {
520 return false;
521 }
522
523 Nat ne = nat_from_hash<Nat>(pm.tagged_mso_bytes_.data(),
524 pm.tagged_mso_bytes_.size());
525 e_ = ec_.f_.to_montgomery(ne);
526
527 // Parse (r,s).
528 const size_t l = pm.sig_.len;
529 Nat nr = nat_from_be<Nat>(&mdoc[pm.sig_.pos]);
530 Nat ns = nat_from_be<Nat>(&mdoc[pm.sig_.pos + l / 2]);
531 ew_.compute_witness(pkX, pkY, ne, nr, ns);
532
533 Nat ne2 = compute_transcript_hash<Nat>(transcript, tlen, &pm.doc_type_);
534 const size_t l2 = pm.dksig_.len;
535 Nat nr2 = nat_from_be<Nat>(&mdoc[pm.dksig_.pos]);
536 Nat ns2 = nat_from_be<Nat>(&mdoc[pm.dksig_.pos + l2 / 2]);
537 size_t pmso = pm.t_mso_.pos + 5; /* skip the tag */
538 dpkx_ = ec_.f_.to_montgomery(
539 nat_from_be<Nat>(&mdoc[pmso + pm.dev_key_pkx_.pos]));
540 dpky_ = ec_.f_.to_montgomery(
541 nat_from_be<Nat>(&mdoc[pmso + pm.dev_key_pky_.pos]));
542 e2_ = ec_.f_.to_montgomery(ne2);
543 dkw_.compute_witness(dpkx_, dpky_, ne2, nr2, ns2);
544 return true;
545 }
546};
547
548// EC: implements the elliptic curve for the mdoc
549// Field: implements the field used to define the sumcheck circuit, which can
550// be smaller than the EC field
551 template <typename EC, typename Field>
552 class MdocHashWitness {
553 using ECField = typename EC::Field;
554 using ECElt = typename ECField::Elt;
555 using ECNat = typename ECField::N;
556 using Elt = typename Field::Elt;
557 using vindex = std::array<Elt, kCborIndexBits>;
558
559 const EC& ec_;
560 const Field& fn_;
561
562 public:
563 ECElt e_; /* Issuer signature values. */
564 ECElt dpkx_, dpky_; /* device key */
565 uint8_t signed_bytes_[kMaxSHABlocks * 64];
566 uint8_t numb_; /* Number of the correct sha block. */
567
568 size_t num_attr_;
569 std::vector<std::vector<uint8_t>> attr_bytes_;
570 std::vector<std::vector<FlatSHA256Witness::BlockWitness>> atw_;
571
572 std::vector<uint8_t> attr_n_; /* All attributes currently require 2 SHA. */
573 std::vector<CborIndex> attr_mso_; /* The cbor indices of the attributes. */
574 std::vector<AttrShift> attr_ei_;
575 std::vector<AttrShift> attr_ev_;
576
577 FlatSHA256Witness::BlockWitness bw_[kMaxSHABlocks];
578
579 ParsedMdoc pm_;
580
581 uint8_t now_[20]; /* CBOR-formatted time used for expiry comparison. */
582
583 explicit MdocHashWitness(size_t num_attr, const EC& ec, const Field& Fn)
584 : ec_(ec), fn_(Fn), num_attr_(num_attr) {}
585
586 void fill_cbor_index(DenseFiller<Field>& df, const CborIndex& ind) const {
587 df.push_back(ind.k, kCborIndexBits, fn_);
588 }
589
590 void fill_attr_shift(DenseFiller<Field>& df, const AttrShift& attr) const {
591 df.push_back(attr.offset, kCborIndexBits, fn_);
592 df.push_back(attr.len, kCborIndexBits, fn_);
593 }
594
595 void fill_sha(DenseFiller<Field>& filler,
596 const FlatSHA256Witness::BlockWitness& bw) const {
597 BitPluckerEncoder<Field, kSHAPluckerBits> BPENC(fn_);
598 for (size_t k = 0; k < 48; ++k) {
599 filler.push_back(BPENC.mkpacked_v32(bw.outw[k]));
600 }
601 for (size_t k = 0; k < 64; ++k) {
602 filler.push_back(BPENC.mkpacked_v32(bw.oute[k]));
603 filler.push_back(BPENC.mkpacked_v32(bw.outa[k]));
604 }
605 for (size_t k = 0; k < 8; ++k) {
606 filler.push_back(BPENC.mkpacked_v32(bw.h1[k]));
607 }
608 }
609
610 void fill_witness(DenseFiller<Field>& filler) const {
611 // Fill sha of main mso.
612 filler.push_back(numb_, 8, fn_);
613 // Don't push the prefix.
614 for (size_t i = kCose1PrefixLen; i < kMaxSHABlocks * 64; ++i) {
615 filler.push_back(signed_bytes_[i], 8, fn_);
616 }
617 for (size_t j = 0; j < kMaxSHABlocks; j++) {
618 fill_sha(filler, bw_[j]);
619 }
620 // === done with sha
621
622 fill_cbor_index(filler, pm_.valid_from_);
623 fill_cbor_index(filler, pm_.valid_until_);
624 fill_cbor_index(filler, pm_.dev_key_info_);
625 fill_cbor_index(filler, pm_.value_digests_);
626
627 // Fill all attribute witnesses.
628 for (size_t ai = 0; ai < num_attr_; ++ai) {
629 for (size_t i = 0; i < 2 * 64; ++i) {
630 filler.push_back(attr_bytes_[ai][i], 8, fn_);
631 }
632 for (size_t j = 0; j < 2; j++) {
633 fill_sha(filler, atw_[ai][j]);
634 }
635
636 // In the case of attribute mso, push the value to avoid having to
637 // deal with 1- or 2- byte key length.
638 filler.push_back(attr_mso_[ai].v, kCborIndexBits, fn_);
639 fill_attr_shift(filler, attr_ei_[ai]);
640 fill_attr_shift(filler, attr_ev_[ai]);
641 }
642 }
643
644 bool compute_witness(const uint8_t mdoc[/* len */], size_t len,
645 const uint8_t transcript[/* tlen */], size_t tlen,
646 const RequestedAttribute attrs[], size_t attrs_len,
647 const uint8_t tnow[/*20*/], size_t version) {
648 if (!pm_.parse_device_response(len, mdoc)) {
649 log(ERROR, "Failed to parse device response");
650 return false;
651 }
652
653 std::vector<uint8_t> buf;
654 if (pm_.t_mso_.len >= kMaxSHABlocks * 64 - 9 - kCose1PrefixLen) {
655 log(ERROR, "tagged mso is too big: %zu", pm_.t_mso_.len);
656 return false;
657 }
658
659 buf.assign(std::begin(kCose1Prefix), std::end(kCose1Prefix));
660 // Add 2-byte length
661 buf.push_back((pm_.t_mso_.len >> 8) & 0xff);
662 buf.push_back(pm_.t_mso_.len & 0xff);
663 for (size_t i = 0; i < pm_.t_mso_.len; ++i) {
664 buf.push_back(mdoc[pm_.t_mso_.pos + i]);
665 }
666
667 FlatSHA256Witness::transform_and_witness_message(
668 buf.size(), buf.data(), kMaxSHABlocks, numb_, signed_bytes_, bw_);
669
670 ECNat ne = nat_from_u32<ECNat>(bw_[numb_ - 1].h1);
671 e_ = ec_.f_.to_montgomery(ne);
672
673 memcpy(now_, tnow, 20);
674
675 size_t pmso = pm_.t_mso_.pos + 5; /* +5 to skip the tag */
676 dpkx_ = ec_.f_.to_montgomery(
677 nat_from_be<ECNat>(&mdoc[pmso + pm_.dev_key_pkx_.pos]));
678 dpky_ = ec_.f_.to_montgomery(
679 nat_from_be<ECNat>(&mdoc[pmso + pm_.dev_key_pky_.pos]));
680
681 // initialize variables
682 attr_n_.resize(attrs_len);
683 attr_mso_.resize(attrs_len);
684 attr_ev_.resize(attrs_len);
685 attr_ei_.resize(attrs_len);
686 attr_bytes_.resize(attrs_len);
687 atw_.resize(attrs_len);
688
689 // Match the attributes with the witnesses from the deviceResponse.
690 for (size_t i = 0; i < attrs_len; ++i) {
691 attr_bytes_[i].resize(128);
692 atw_[i].resize(2);
693 bool found = false;
694 for (auto fa : pm_.attributes_) {
695 if (fa == attrs[i]) {
696 FlatSHA256Witness::transform_and_witness_message(
697 fa.tag_len, &fa.doc[fa.tag_ind], 2, attr_n_[i],
698 &attr_bytes_[i][0], &atw_[i][0]);
699 attr_mso_[i] = fa.mso;
700 attr_ei_[i].offset = fa.id_ind - fa.tag_ind;
701 if (version >= 4) {
702 // In version 4, the attribute id is encoded as the length of the
703 // id followed by the id. The witness starts at the id, so we
704 // subtract 1 or 2 to get the offset, depending on the id length.
705 attr_ei_[i].offset -= 1;
706 if (fa.id_len > 23) {
707 attr_ei_[i].offset -= 1;
708 }
709 }
710 attr_ei_[i].len = fa.witness_length(attrs[i]);
711 attr_ev_[i].offset = fa.val_ind - fa.tag_ind;
712 attr_ev_[i].len = fa.val_len;
713 found = true;
714 break;
715 }
716 }
717 if (!found) {
718 log(ERROR, "Could not find attribute %.*s", attrs[i].id_len,
719 attrs[i].id);
720 return false;
721 }
722 }
723 return true;
724 }
725};
726} // namespace proofs
727
728#endif // PRIVACY_PROOFS_ZK_LIB_CIRCUITS_MDOC_MDOC_WITNESS_H_
proofs::BitPluckerEncoder
Definition bit_plucker_encoder.h:27
proofs::CborDoc
Definition host_decoder.h:47
proofs::DenseFiller
Definition dense.h:153
proofs::GF2_128
Definition gf2_128.h:35
proofs::MacWitness
Definition mac_witness.h:28
proofs::Nat
Definition nat.h:60
proofs::ParsedMdoc
Definition mdoc_witness.h:84
proofs::SHA256
Definition crypto.h:40
proofs::VerifyWitness3
Definition verify_witness.h:30
RequestedAttribute
Definition mdoc_zk.h:39
proofs::AttrShift
Definition mdoc_witness.h:48
proofs::CborIndex
Definition mdoc_witness.h:43
proofs::FlatSHA256Witness::BlockWitness
Definition flatsha256_witness.h:27
proofs::FullAttribute
Definition mdoc_witness.h:55
proofs::GF2_128::Elt
Definition gf2_128.h:63