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