Longfellow ZK 0290cb32

Loading...
Searching...
No Matches
mdoc_hash.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_HASH_H_
16#define PRIVACY_PROOFS_ZK_LIB_CIRCUITS_MDOC_MDOC_HASH_H_
17
18#include <cstddef>
19#include <cstdint>
20#include <vector>
21
22#include "circuits/compiler/compiler.h"
23#include "circuits/logic/bit_plucker.h"
24#include "circuits/logic/memcmp.h"
25#include "circuits/logic/routing.h"
26#include "circuits/mdoc/mdoc_constants.h"
27#include "circuits/sha/flatsha256_circuit.h"
28
29 namespace proofs {
30
31 static constexpr size_t kSHAPluckerBits = 4u;
32
33// This class (only) verifies the hashing and pseudo-parsing of an mdoc.
34// Specifically, it checks
35// (a) the hash of the mdoc matches its mac.
36// (b) the values dpk_{x,y} appear in approximate cbor form, and their macs
37// match the expected values
38// (c) validFrom, validUntil appear in approximate cbor form, and that
39// validFrom <= now <= validUntil
40// (d) For each expected attribute, there exists a preimage to a sha hash
41// that appears in the mso, the preimage is approximately cbor formatted,
42// and the preimage includes the expected attribute id and value.
43 template <class LogicCircuit, class Field>
44 class MdocHash {
45 using v8 = typename LogicCircuit::v8;
46 using v32 = typename LogicCircuit::v32;
47 using v256 = typename LogicCircuit::v256;
48
49 using vind = typename LogicCircuit::template bitvec<kCborIndexBits>;
50
51 using Flatsha = FlatSHA256Circuit<LogicCircuit,
52 BitPlucker<LogicCircuit, kSHAPluckerBits>>;
53 using ShaBlockWitness = typename Flatsha::BlockWitness;
54 using sha_packed_v32 = typename Flatsha::packed_v32;
55
56 public:
57 // These structures mimic the similarly named structures in Witness, but
58 // their members are circuit wire objects instead of size_t.
59 struct OpenedAttribute {
60 v8 attr[32]; /* 32b representing attribute name in be. */
61 v8 v1[64]; /* 64b of attribute value */
62 v8 len; /* public length of the encoded attribute id and value */
63 void input(const LogicCircuit& lc) {
64 for (size_t j = 0; j < 32; ++j) {
65 attr[j] = lc.template vinput<8>();
66 }
67 for (size_t j = 0; j < 64; ++j) {
68 v1[j] = lc.template vinput<8>();
69 }
70 len = lc.template vinput<8>();
71 }
72 };
73 struct CborIndex {
74 vind k;
75 void input(const LogicCircuit& lc) {
76 k = lc.template vinput<kCborIndexBits>();
77 }
78 };
79
80 struct AttrShift {
81 vind offset;
82 vind len;
83 void input(const LogicCircuit& lc) {
84 offset = lc.template vinput<kCborIndexBits>();
85 len = lc.template vinput<kCborIndexBits>();
86 }
87 };
88
89 class Witness {
90 public:
91 v8 in_[64 * kMaxSHABlocks]; /* input bytes, 64 * MAX */
92
93 v8 nb_; /* index of sha block that contains the real hash */
94 ShaBlockWitness sig_sha_[kMaxSHABlocks];
95
96 std::vector<std::vector<ShaBlockWitness>> attr_sha_;
97 std::vector<std::vector<v8>> attrb_;
98
99 CborIndex valid_from_, valid_until_;
100 CborIndex dev_key_info_;
101 CborIndex value_digests_;
102 std::vector<CborIndex> attr_mso_;
103 std::vector<AttrShift> attr_ei_;
104 std::vector<AttrShift> attr_ev_;
105 size_t num_attr_;
106
107 explicit Witness(size_t num_attr) {
108 num_attr_ = num_attr;
109 attr_mso_.resize(num_attr);
110 attr_ei_.resize(num_attr);
111 attr_ev_.resize(num_attr);
112 attr_sha_.resize(num_attr);
113 for (size_t i = 0; i < num_attr; ++i) {
114 attr_sha_[i].resize(2);
115 }
116
117 attrb_.resize(num_attr);
118 }
119
120 void input(QuadCircuit<Field>& Q, const LogicCircuit& lc) {
121 nb_ = lc.template vinput<8>();
122
123 // sha input init =========================
124 for (size_t i = 0; i + kCose1PrefixLen < 64 * kMaxSHABlocks; ++i) {
125 in_[i] = lc.template vinput<8>();
126 }
127 for (size_t j = 0; j < kMaxSHABlocks; j++) {
128 sig_sha_[j].input(Q);
129 }
130
131 valid_from_.input(lc);
132 valid_until_.input(lc);
133 dev_key_info_.input(lc);
134 value_digests_.input(lc);
135
136 // Attribute opening witnesses
137 for (size_t ai = 0; ai < num_attr_; ++ai) {
138 for (size_t i = 0; i < 64 * 2; ++i) {
139 attrb_[ai].push_back(lc.template vinput<8>());
140 }
141 for (size_t j = 0; j < 2; j++) {
142 attr_sha_[ai][j].input(Q);
143 }
144 attr_mso_[ai].input(lc);
145 attr_ei_[ai].input(lc);
146 attr_ev_[ai].input(lc);
147 }
148 }
149 };
150
151 explicit MdocHash(const LogicCircuit& lc) : lc_(lc), sha_(lc), r_(lc) {}
152
153 void assert_valid_hash_mdoc(OpenedAttribute oa[/* NUM_ATTR */],
154 const v8 now[/*20*/], const v256& e,
155 const v256& dpkx, const v256& dpky,
156 const Witness& vw) const {
157 sha_.assert_message_hash_with_prefix(kMaxSHABlocks, vw.nb_, vw.in_,
158 kCose1Prefix, kCose1PrefixLen, e,
159 vw.sig_sha_);
160
161 // Shift a portion of the MSO into buf and check it.
162 const v8 zz = lc_.template vbit<8>(0); // cannot appear in strings
163 std::vector<v8> cmp_buf(kMaxMsoLen);
164 const Memcmp<LogicCircuit> CMP(lc_);
165
166 // In the shifting below, the +5 corresponds to the prefix
167 // D8 18 <len2> prefix of the mso that we want to skip parsing.
168 // The +2 corresponds to the length.
169
170 // validFrom <= now
171 r_.shift(vw.valid_from_.k, kValidFromLen + kDateLen, &cmp_buf[0],
172 kMaxMsoLen, vw.in_ + 5 + 2, zz, /*unroll=*/3);
173 assert_bytes_at(kValidFromLen, &cmp_buf[0], kValidFromCheck);
174 auto cmp = CMP.leq(kDateLen, &cmp_buf[kValidFromLen], &now[0]);
175 lc_.assert1(cmp);
176
177 // now <= validUntil
178 r_.shift(vw.valid_until_.k, kValidUntilLen + kDateLen, &cmp_buf[0],
179 kMaxMsoLen, vw.in_ + 5 + 2, zz, /*unroll=*/3);
180 assert_bytes_at(kValidUntilLen, &cmp_buf[0], kValidUntilCheck);
181 cmp = CMP.leq(kDateLen, &now[0], &cmp_buf[kValidUntilLen]);
182 lc_.assert1(cmp);
183
184 // DPK_{x,y}
185 r_.shift(vw.dev_key_info_.k, kDeviceKeyInfoLen + 3 + 32 + 32, &cmp_buf[0],
186 kMaxMsoLen, vw.in_ + 5 + 2, zz, /*unroll=*/3);
187 assert_bytes_at(kDeviceKeyInfoLen, &cmp_buf[0], kDeviceKeyInfoCheck);
188 uint8_t dpkyCheck[] = {0x22, 0x58, 0x20};
189 assert_bytes_at(sizeof(dpkyCheck), &cmp_buf[65], dpkyCheck);
190
191 assert_key(dpkx, &cmp_buf[kPkxInd]);
192 assert_key(dpky, &cmp_buf[kPkyInd]);
193
194 // Attributes parsing
195 // valueDigests, ignore byte 13 \in {A1,A2} representing map size.
196 r_.shift(vw.value_digests_.k, kValueDigestsLen, cmp_buf.data(), kMaxMsoLen,
197 vw.in_ + 5 + 2, zz, /*unroll=*/3);
198 assert_bytes_at(13, &cmp_buf[0], kValueDigestsCheck);
199
200 // Attributes: Equality of hash with MSO value
201 for (size_t ai = 0; ai < vw.num_attr_; ++ai) {
202 v8 B[96];
203 // Check the hash matches the value in the signed MSO.
204 r_.shift(vw.attr_mso_[ai].k, 2 + 32, &cmp_buf[0], kMaxMsoLen,
205 vw.in_ + 5 + 2, zz, /*unroll=*/3);
206
207 // Basic CBOR check of the Tag
208 assert_bytes_at(2, &cmp_buf[0], kTag32);
209
210 v256 mm;
211 // The loop below accounts for endian and v256 vs v8 types.
212 for (size_t j = 0; j < 256; ++j) {
213 mm[j] = cmp_buf[2 + (255 - j) / 8][(j % 8)];
214 }
215
216 auto two = lc_.template vbit<8>(2);
217 sha_.assert_message_hash(2, two, vw.attrb_[ai].data(), mm,
218 vw.attr_sha_[ai].data());
219
220 // Check that the attribute_id and value occur in the hashed text.
221 r_.shift(vw.attr_ei_[ai].offset, 96, B, 128, vw.attrb_[ai].data(), zz, 3);
222 assert_attribute(96, oa[ai].len, B, oa[ai]);
223 }
224 }
225
226 private:
227 void assert_bytes_at(size_t len, const v8 buf[/*>=len*/],
228 const uint8_t want[/*len*/]) const {
229 for (size_t i = 0; i < len; ++i) {
230 auto want_i = lc_.template vbit<8>(want[i]);
231 lc_.vassert_eq(&buf[i], want_i);
232 }
233 }
234
235 // Checks that an attribute id or attribute value is as expected.
236 // The len parameter holds the byte length of the expected id or value.
237 void assert_attribute(size_t max, const v8& len, const v8 got[/*max*/],
238 const OpenedAttribute& oa) const {
239 // Copy the attribute id and value into a single array.
240 v8 want[96];
241 for (size_t j = 0; j < 32; ++j) {
242 want[j] = oa.attr[j];
243 }
244 for (size_t j = 0; j < 64; ++j) {
245 want[32 + j] = oa.v1[j];
246 }
247
248 // Perform an equality check on the first len bytes.
249 for (size_t j = 0; j < max; ++j) {
250 auto ll = lc_.vlt(j, len);
251 auto same = lc_.eq(8, got[j].data(), want[j].data());
252 lc_.assert_implies(&ll, same);
253 }
254 }
255
256 // Asserts that the key is equal to the value in big-endian order in buf_be.
257 void assert_key(v256 key, const v8 buf_be[/*32*/]) const {
258 v256 m;
259 for (size_t i = 0; i < 256; ++i) {
260 m[i] = buf_be[31 - (i / 8)][i % 8];
261 }
262 lc_.vassert_eq(&m, key);
263 }
264
265 // The constants below define the prefix of each field that is verified
266 // in the MDOC. This string matching approach is substantially faster than
267 // parsing the MDOC into cbor, and its soundness analysis provides at least 96
268 // bits of static security. These constants differ from similarly named ones
269 // in mdoc_constants because they include header bytes; the mdoc_constants
270 // values are used for cbor parsing of the raw mdoc, whereas these are used by
271 // the circuit.
272
273 // 69 [text(9)] 76616C696446726F6D [validFrom] C0 [tag(0)] 74 [len 20]
274 static constexpr uint8_t kValidFromCheck[] = {
275 0x69, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x46, 0x72, 0x6F, 0x6D, 0xC0, 0x74};
276 static constexpr size_t kValidFromLen = sizeof(kValidFromCheck);
277
278 // 6A [text(10)] 76616C6964556E74696C [validUntil] C0 [tag(0)] 74
279 static constexpr uint8_t kValidUntilCheck[] = {0x6A, 0x76, 0x61, 0x6C, 0x69,
280 0x64, 0x55, 0x6E, 0x74, 0x69,
281 0x6C, 0xC0, 0x74};
282 static constexpr size_t kValidUntilLen = sizeof(kValidUntilCheck);
283
284 // 6D text(13) 6465766963654B6579496E666F "deviceKeyInfo"
285 // A1 map(1) 69 text(9) 6465766963654B6579 "deviceKey"
286 // A4 map(4) 01 02 20 01
287 // 21 negative(1) 58 20 bytes(32)
288 // <dpkx>
289 // 22 negative(2) 58 20 bytes(32)
290 // <dpky>
291 static constexpr uint8_t kDeviceKeyInfoCheck[] = {
292 0x6D, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4B, 0x65, 0x79, 0x49,
293 0x6E, 0x66, 0x6F, 0xA1, 0x69, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
294 0x4B, 0x65, 0x79, 0xA4, 0x01, 0x02, 0x20, 0x01, 0x21, 0x58, 0x20};
295 static constexpr size_t kDeviceKeyInfoLen = sizeof(kDeviceKeyInfoCheck);
296 static constexpr size_t kPkxInd = kDeviceKeyInfoLen;
297 static constexpr size_t kPkyInd = 68; /* 64 + 3 byte tag + 1*/
298
299 // 6C text(12) 76616C756544696765737473 "valueDigests" A{1,2} # map(1,2)
300 // 71 text(17) 6F72672E69736F2E31383031332E352E31 "org.iso.18013.5.1"
301 static constexpr uint8_t kValueDigestsCheck[] = {
302 0x6C, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x44, 0x69, 0x67,
303 0x65, 0x73, 0x74, 0x73,
304 };
305 static constexpr size_t kValueDigestsLen = sizeof(kValueDigestsCheck);
306
307 static constexpr size_t kDateLen = 20;
308
309 const LogicCircuit& lc_;
310 Flatsha sha_;
311 Routing<LogicCircuit> r_;
312};
313
314} // namespace proofs
315
316#endif // PRIVACY_PROOFS_ZK_LIB_CIRCUITS_MDOC_MDOC_HASH_H_
proofs::BitPlucker
Definition bit_plucker.h:86
proofs::FlatSHA256Circuit
Definition flatsha256_circuit.h:52
proofs::Memcmp
Definition memcmp.h:30
proofs::QuadCircuit
Definition compiler.h:50
proofs::MdocHash::AttrShift
Definition mdoc_hash.h:80
proofs::MdocHash::CborIndex
Definition mdoc_hash.h:73
proofs::MdocHash::OpenedAttribute
Definition mdoc_hash.h:59
proofs::OpenedAttribute
Definition jwt_witness.h:37