1use crate::{error, SDJWTJson, SDJWTSerializationFormat};
6use error::{Error, Result};
7use jsonwebtoken::{Algorithm, EncodingKey, Header};
8use serde_json::{Map, Value};
9use std::collections::HashMap;
10use std::ops::Add;
11use std::str::FromStr;
12use std::time;
13
14use crate::utils::base64_hash;
15use crate::SDJWTCommon;
16use crate::{
17 COMBINED_SERIALIZATION_FORMAT_SEPARATOR, DEFAULT_SIGNING_ALG, KB_DIGEST_KEY, SD_DIGESTS_KEY,
18 SD_LIST_PREFIX,
19};
20
21pub struct SDJWTHolder {
22 sd_jwt_engine: SDJWTCommon,
23 hs_disclosures: Vec<String>,
24 key_binding_jwt_header: HashMap<String, Value>,
25 key_binding_jwt_payload: HashMap<String, Value>,
26 serialized_key_binding_jwt: String,
27 sd_jwt_payload: Map<String, Value>,
28 serialized_sd_jwt: String,
29 sd_jwt_json: Option<SDJWTJson>,
30}
31
32impl SDJWTHolder {
33 pub fn new(sd_jwt_with_disclosures: String, serialization_format: SDJWTSerializationFormat) -> Result<Self> {
47 let mut holder = SDJWTHolder {
48 sd_jwt_engine: SDJWTCommon {
49 serialization_format,
50 ..Default::default()
51 },
52 hs_disclosures: Vec::new(),
53 key_binding_jwt_header: HashMap::new(),
54 key_binding_jwt_payload: HashMap::new(),
55 serialized_key_binding_jwt: "".to_string(),
56 sd_jwt_payload: Map::new(),
57 serialized_sd_jwt: "".to_string(),
58 sd_jwt_json: None,
59 };
60
61 holder
62 .sd_jwt_engine
63 .parse_sd_jwt(sd_jwt_with_disclosures.clone())?;
64
65 holder.sd_jwt_payload = holder
67 .sd_jwt_engine
68 .unverified_input_sd_jwt_payload
69 .take()
70 .ok_or(Error::InvalidState("Cannot take payload".to_string()))?;
71 holder.serialized_sd_jwt = holder
72 .sd_jwt_engine
73 .unverified_sd_jwt
74 .take()
75 .ok_or(Error::InvalidState("Cannot take jwt".to_string()))?;
76 holder.sd_jwt_json = holder.sd_jwt_engine.unverified_sd_jwt_json.clone();
77
78 holder.sd_jwt_engine.create_hash_mappings()?;
79
80 Ok(holder)
81 }
82
83 pub fn create_presentation(
95 &mut self,
96 claims_to_disclose: Map<String, Value>,
97 nonce: Option<String>,
98 aud: Option<String>,
99 holder_key: Option<EncodingKey>,
100 sign_alg: Option<String>,
101 ) -> Result<String> {
102 self.key_binding_jwt_header = Default::default();
103 self.key_binding_jwt_payload = Default::default();
104 self.serialized_key_binding_jwt = Default::default();
105 self.hs_disclosures = self.select_disclosures(&self.sd_jwt_payload, claims_to_disclose)?;
106
107 match (nonce, aud, holder_key) {
108 (Some(nonce), Some(aud), Some(holder_key)) => {
109 self.create_key_binding_jwt(nonce, aud, &holder_key, sign_alg)?
110 }
111 (None, None, None) => {}
112 _ => {
113 return Err(Error::InvalidInput(
114 "Inconsistency in parameters to determine JWT KB by holder".to_string(),
115 ));
116 }
117 }
118
119 let sd_jwt_presentation = if self.sd_jwt_engine.serialization_format == SDJWTSerializationFormat::Compact {
120 let mut combined: Vec<&str> = Vec::with_capacity(self.hs_disclosures.len() + 2);
121 combined.push(&self.serialized_sd_jwt);
122 combined.extend(self.hs_disclosures.iter().map(|s| s.as_str()));
123 combined.push(&self.serialized_key_binding_jwt);
124 let joined = combined.join(COMBINED_SERIALIZATION_FORMAT_SEPARATOR);
125 joined.to_string()
126 } else {
127 let mut sd_jwt_json = self
128 .sd_jwt_json
129 .take()
130 .ok_or(Error::InvalidState("Cannot take SDJWTJson".to_string()))?;
131 sd_jwt_json.disclosures = self.hs_disclosures.clone();
132 if !self.serialized_key_binding_jwt.is_empty() {
133 sd_jwt_json.kb_jwt = Some(self.serialized_key_binding_jwt.clone());
134 }
135 serde_json::to_string(&sd_jwt_json)
136 .map_err(|e| Error::DeserializationError(e.to_string()))?
137 };
138
139 Ok(sd_jwt_presentation)
140 }
141
142 fn select_disclosures(
143 &self,
144 sd_jwt_claims: &Map<String, Value>,
145 claims_to_disclose: Map<String, Value>,
146 ) -> Result<Vec<String>> {
147 let mut hash_to_disclosure = Vec::new();
148 let default_list = Vec::new();
149 let sd_map: HashMap<&str, (&Value, &str)> = sd_jwt_claims
150 .get(SD_DIGESTS_KEY)
151 .and_then(Value::as_array)
152 .unwrap_or(&default_list)
153 .iter()
154 .filter_map(|digest| {
155 let digest = match digest.as_str() {
156 Some(digest) => digest,
157 None => return None,
158 };
159 if let Some(Value::Array(disclosure)) =
160 self.sd_jwt_engine.hash_to_decoded_disclosure.get(digest)
161 {
162 let key = match disclosure[1].as_str() {
163 Some(digest) => digest,
164 None => return None,
165 };
166 return Some((key, (&disclosure[2], digest)));
167 }
168 None
169 })
170 .collect(); for (key_to_disclose, value_to_disclose) in claims_to_disclose {
172 match value_to_disclose {
173 Value::Bool(true) | Value::Number(_) | Value::String(_) => {
174 }
176 Value::Array(claims_to_disclose) => {
177 if let Some(sd_jwt_claims) = sd_jwt_claims
178 .get(&key_to_disclose)
179 .and_then(Value::as_array)
180 {
181 hash_to_disclosure.append(
182 &mut self.select_disclosures_from_disclosed_list(
183 sd_jwt_claims,
184 &claims_to_disclose,
185 )?,
186 )
187 } else if let Some(sd_jwt_claims) = sd_map
188 .get(key_to_disclose.as_str())
189 .and_then(|(sd, _)| sd.as_array())
190 {
191 hash_to_disclosure.append(
192 &mut self.select_disclosures_from_disclosed_list(
193 sd_jwt_claims,
194 &claims_to_disclose,
195 )?,
196 )
197 }
198 }
199 Value::Object(claims_to_disclose) if (!claims_to_disclose.is_empty()) => {
200 let sd_jwt_claims = if let Some(next) = sd_jwt_claims
201 .get(&key_to_disclose)
202 .and_then(Value::as_object)
203 {
204 next
205 } else {
206 sd_map[key_to_disclose.as_str()]
207 .0
208 .as_object()
209 .ok_or(Error::ConversionError("json object".to_string()))?
210 };
211 hash_to_disclosure
212 .append(&mut self.select_disclosures(sd_jwt_claims, claims_to_disclose)?);
213 }
214 Value::Object(_) => { }
215 Value::Bool(false) | Value::Null => {
216 continue;
218 }
219 }
220 if sd_jwt_claims.contains_key(&key_to_disclose) {
221 continue;
222 } else if let Some((_, digest)) = sd_map.get(key_to_disclose.as_str()) {
223 hash_to_disclosure.push(self.sd_jwt_engine.hash_to_disclosure[*digest].to_owned());
224 } else {
225 return Err(Error::InvalidState(
226 "Requested claim doesn't exist".to_string(),
227 ));
228 }
229 }
230
231 Ok(hash_to_disclosure)
232 }
233
234 fn select_disclosures_from_disclosed_list(
235 &self,
236 sd_jwt_claims: &[Value],
237 claims_to_disclose: &[Value],
238 ) -> Result<Vec<String>> {
239 let mut hash_to_disclosure: Vec<String> = Vec::new();
240 for (claim_to_disclose, sd_jwt_claims) in claims_to_disclose.iter().zip(sd_jwt_claims) {
241 match (claim_to_disclose, sd_jwt_claims) {
242 (Value::Bool(true), Value::Object(sd_jwt_claims)) => {
243 if let Some(Value::String(digest)) = sd_jwt_claims.get(SD_LIST_PREFIX) {
244 hash_to_disclosure
245 .push(self.sd_jwt_engine.hash_to_disclosure[digest].to_owned());
246 }
247 }
248 (claim_to_disclose, Value::Object(sd_jwt_claims)) => {
249 if let Some(Value::String(digest)) = sd_jwt_claims.get(SD_LIST_PREFIX) {
250 let disclosure = self.sd_jwt_engine.hash_to_decoded_disclosure[digest]
251 .as_array()
252 .ok_or(Error::ConversionError("json array".to_string()))?;
253 match (claim_to_disclose, disclosure.get(1)) {
254 (
255 Value::Array(claim_to_disclose),
256 Some(Value::Array(sd_jwt_claims)),
257 ) => {
258 hash_to_disclosure.push(
259 self.sd_jwt_engine.hash_to_disclosure[digest].clone()
260 );
261 hash_to_disclosure.append(
262 &mut self.select_disclosures_from_disclosed_list(
263 sd_jwt_claims,
264 claim_to_disclose,
265 )?,
266 );
267 }
268 (
269 Value::Object(claim_to_disclose),
270 Some(Value::Object(sd_jwt_claims)),
271 ) => {
272 hash_to_disclosure
273 .push(self.sd_jwt_engine.hash_to_disclosure[digest].to_owned());
274 hash_to_disclosure.append(&mut self.select_disclosures(
275 sd_jwt_claims,
276 claim_to_disclose.to_owned(),
277 )?);
278 }
279 _ => {}
280 }
281 } else if let Some(claim_to_disclose) = claim_to_disclose.as_object() {
282 hash_to_disclosure.append(
283 &mut self
284 .select_disclosures(sd_jwt_claims, claim_to_disclose.to_owned())?,
285 );
286 }
287 }
288 (Value::Array(claim_to_disclose), Value::Array(sd_jwt_claims)) => {
289 hash_to_disclosure.append(&mut self.select_disclosures_from_disclosed_list(
290 sd_jwt_claims,
291 claim_to_disclose,
292 )?);
293 }
294 _ => {}
295 }
296 }
297
298 Ok(hash_to_disclosure)
299 }
300 fn create_key_binding_jwt(
301 &mut self,
302 nonce: String,
303 aud: String,
304 holder_key: &EncodingKey,
305 sign_alg: Option<String>,
306 ) -> Result<()> {
307 let alg = sign_alg.unwrap_or_else(|| DEFAULT_SIGNING_ALG.to_string());
308 self.key_binding_jwt_header
310 .insert("alg".to_string(), alg.clone().into());
311 self.key_binding_jwt_header
312 .insert("typ".to_string(), crate::KB_JWT_TYP_HEADER.into());
313 self.key_binding_jwt_payload
314 .insert("nonce".to_string(), nonce.into());
315 self.key_binding_jwt_payload
316 .insert("aud".to_string(), aud.into());
317 let timestamp = time::SystemTime::now()
318 .duration_since(time::UNIX_EPOCH)
319 .map_err(|e| Error::ConversionError(format!("timestamp: {}", e)))?
320 .as_secs();
321 self.key_binding_jwt_payload
322 .insert("iat".to_string(), timestamp.into());
323 self.set_key_binding_digest_key()?;
324 let mut header = Header::new(
326 Algorithm::from_str(alg.as_str())
327 .map_err(|e| Error::DeserializationError(e.to_string()))?,
328 );
329 header.typ = Some(crate::KB_JWT_TYP_HEADER.into());
330 self.serialized_key_binding_jwt =
331 jsonwebtoken::encode(&header, &self.key_binding_jwt_payload, holder_key)
332 .map_err(|e| Error::DeserializationError(e.to_string()))?;
333 Ok(())
334 }
335
336 fn set_key_binding_digest_key(&mut self) -> Result<()> {
337 let mut combined: Vec<&str> = Vec::with_capacity(self.hs_disclosures.len() + 1);
338 combined.push(&self.serialized_sd_jwt);
339 combined.extend(self.hs_disclosures.iter().map(|s| s.as_str()));
340 let combined = combined
341 .join(COMBINED_SERIALIZATION_FORMAT_SEPARATOR)
342 .add(COMBINED_SERIALIZATION_FORMAT_SEPARATOR);
343
344 let sd_hash = base64_hash(combined.as_bytes());
345 self.key_binding_jwt_payload
346 .insert(KB_DIGEST_KEY.to_owned(), Value::String(sd_hash));
347
348 Ok(())
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use crate::issuer::ClaimsForSelectiveDisclosureStrategy;
355 use crate::{SDJWTHolder, SDJWTIssuer, COMBINED_SERIALIZATION_FORMAT_SEPARATOR, SDJWTSerializationFormat};
356 use jsonwebtoken::EncodingKey;
357 use serde_json::{json, Map, Value};
358 use std::collections::HashSet;
359
360 const PRIVATE_ISSUER_PEM: &str = "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgUr2bNKuBPOrAaxsR\nnbSH6hIhmNTxSGXshDSUD1a1y7ihRANCAARvbx3gzBkyPDz7TQIbjF+ef1IsxUwz\nX1KWpmlVv+421F7+c1sLqGk4HUuoVeN8iOoAcE547pJhUEJyf5Asc6pP\n-----END PRIVATE KEY-----\n";
361
362 #[test]
363 fn create_full_presentation() {
364 let user_claims = json!({
365 "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
366 "iss": "https://example.com/issuer",
367 "iat": "1683000000",
368 "exp": "1883000000",
369 "address": {
370 "street_address": "Schulstr. 12",
371 "locality": "Schulpforta",
372 "region": "Sachsen-Anhalt",
373 "country": "DE"
374 }
375 });
376 let private_issuer_bytes = PRIVATE_ISSUER_PEM.as_bytes();
377 let issuer_key = EncodingKey::from_ec_pem(private_issuer_bytes).unwrap();
378 let sd_jwt = SDJWTIssuer::new(issuer_key, None).issue_sd_jwt(
379 user_claims.clone(),
380 ClaimsForSelectiveDisclosureStrategy::AllLevels,
381 None,
382 false,
383 SDJWTSerializationFormat::Compact,
384 )
385 .unwrap();
386 let presentation = SDJWTHolder::new(
387 sd_jwt.clone(),
388 SDJWTSerializationFormat::Compact,
389 )
390 .unwrap()
391 .create_presentation(
392 user_claims.as_object().unwrap().clone(),
393 None,
394 None,
395 None,
396 None,
397 )
398 .unwrap();
399 assert_eq!(sd_jwt, presentation);
400 }
401 #[test]
402 fn create_presentation_empty_object_as_disclosure_value() {
403 let mut user_claims = json!({
404 "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
405 "iss": "https://example.com/issuer",
406 "iat": 1683000000,
407 "exp": 1883000000,
408 "address": {
409 "street_address": "Schulstr. 12",
410 "locality": "Schulpforta",
411 "region": "Sachsen-Anhalt",
412 "country": "DE"
413 }
414 });
415 let private_issuer_bytes = PRIVATE_ISSUER_PEM.as_bytes();
416 let issuer_key = EncodingKey::from_ec_pem(private_issuer_bytes).unwrap();
417
418 let sd_jwt = SDJWTIssuer::new(issuer_key, None).issue_sd_jwt(
419 user_claims.clone(),
420 ClaimsForSelectiveDisclosureStrategy::AllLevels,
421 None,
422 false,
423 SDJWTSerializationFormat::Compact,
424 )
425 .unwrap();
426 let issued = sd_jwt.clone();
427 user_claims["address"] = Value::Object(Map::new());
428 let presentation =
429 SDJWTHolder::new(sd_jwt, SDJWTSerializationFormat::Compact)
430 .unwrap()
431 .create_presentation(
432 user_claims.as_object().unwrap().clone(),
433 None,
434 None,
435 None,
436 None,
437 )
438 .unwrap();
439
440 let mut parts: Vec<&str> = issued
441 .split(COMBINED_SERIALIZATION_FORMAT_SEPARATOR)
442 .collect();
443 parts.remove(5);
444 parts.remove(4);
445 parts.remove(3);
446 parts.remove(2);
447 let expected = parts.join(COMBINED_SERIALIZATION_FORMAT_SEPARATOR);
448 assert_eq!(expected, presentation);
449 }
450
451 #[test]
452 fn create_presentation_for_arrayed_disclosures() {
453 let mut user_claims = json!(
454 {
455 "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
456 "name": "Bois",
457 "addresses": [
458 {
459 "street_address": "Schulstr. 12",
460 "locality": "Schulpforta",
461 "region": "Sachsen-Anhalt",
462 "country": "DE"
463 },
464 {
465 "street_address": "456 Main St",
466 "locality": "Anytown",
467 "region": "NY",
468 "country": "US"
469 }
470 ],
471 "nationalities": [
472 "US",
473 "CA"
474 ]
475 }
476 );
477 let strategy = ClaimsForSelectiveDisclosureStrategy::Custom(vec![
478 "$.name",
479 "$.addresses[1]",
480 "$.addresses[1].country",
481 "$.nationalities[0]",
482 ]);
483
484 let private_issuer_bytes = PRIVATE_ISSUER_PEM.as_bytes();
485 let issuer_key = EncodingKey::from_ec_pem(private_issuer_bytes).unwrap();
486 let sd_jwt = SDJWTIssuer::new(issuer_key, None).issue_sd_jwt(
487 user_claims.clone(),
488 strategy,
489 None,
490 false,
491 SDJWTSerializationFormat::Compact,
492 )
493 .unwrap();
494 user_claims["addresses"] = Value::Array(vec![Value::Bool(true), Value::Bool(false)]);
496 user_claims["nationalities"] = Value::Array(vec![Value::Bool(true), Value::Bool(true)]);
497
498 let issued = sd_jwt.clone();
499 println!("{}", issued);
500 let presentation =
501 SDJWTHolder::new(sd_jwt, SDJWTSerializationFormat::Compact)
502 .unwrap()
503 .create_presentation(
504 user_claims.as_object().unwrap().clone(),
505 None,
506 None,
507 None,
508 None,
509 )
510 .unwrap();
511 println!("{}", presentation);
512 let mut issued_parts: HashSet<&str> = issued
513 .split(COMBINED_SERIALIZATION_FORMAT_SEPARATOR)
514 .collect();
515 issued_parts.remove("");
516
517 let mut revealed_parts: HashSet<&str> = presentation
518 .split(COMBINED_SERIALIZATION_FORMAT_SEPARATOR)
519 .collect();
520 revealed_parts.remove("");
521
522 let union: HashSet<_> = issued_parts.intersection(&revealed_parts).collect();
523 assert_eq!(union.len(), 3);
524 }
525
526 #[test]
527 fn create_presentation_for_recursive_disclosures() {
528 let sd_jwt = String::from("eyJhbGciOiJFUzI1NiJ9.eyJmb28iOlt7Ii4uLiI6Ii1XMWROTk0tNUI3WlpxR3R4MkF6RTA3X0hpRUpOZVJtNGtEQ1VORTVDNFUifSx7Ii4uLiI6ImpuUURqUEFoclY1bjMtRW5PVEZHWTcwMkd0T3FhN3hua3pVM0E4aElSX3cifV0sImJhciI6eyJfc2QiOlsiX25yZUxad2xVYlp1SmtqS1RVdHR5YkhqUTNrY2J4cnZab1dxUmVBbG4tcyIsImhGcjdBRElQbjZvQ3lSckNBN0VtNldLaGk1UjdXMWJjYWFZUFFrelpGMXciXX0sInF1eCI6W3siLi4uIjoieHl6MkRSSDRTSkpjdFFtMDEtSzROVVllMTMzMWh6U3VkTXd3MENDODEyUSJ9XSwiYmF6IjpbeyIuLi4iOiJRMGcyVmYzNnl6TnNvUkdNb0dsODZnZ2QyWGFVTmg5bGN6STFfbmFZYUhnIn0seyIuLi4iOiJZcGpMNTJKd1BfYmFFS21OaHFLazE3TWFrMl9fSWJCNmctY0haSHd6dmwwIn1dLCJhbmltYWxzIjp7Il9zZCI6WyJyQ19LNzlObG95SkFPWXRCOW9ITFlsTVJSS1V4UTNnaTZ0Wld0Zm90TWRjIiwidjUyd3d6bzB5Ymw2U2V1MjZWYklUODh5bHk1LXVMZkdlYTdkWnMxSHBwMCJdfSwiX3NkX2FsZyI6InNoYS0yNTYifQ.piidRp0pHJYmtExCJnLExaaWMTBX50mLwM6gFVYnD72DszyjpKbAoZhyAXT-I4CqqSpiHZg-2w8s26XBraqX6w~WyJCQ2k5UXlsWVVqVEpXWWVfbzRzOWxnIiwgIm9uZSJd~WyJSLXZ0bDBmbWF6N01zR1ZWRFh3T3BnIiwgInR3byJd~WyJXNWlOQ1Z1Qlo4OW9aV2dIUkxzRWJBIiwgInJlZCIsIDFd~WyJTQW5hNUJnaHJxUXJ0amR5SGxiejJBIiwgImdyZWVuIiwgMl0~WyJENVJrNVlIUkdJVXM5enp6OFUtOTVnIiwgImJsdWUiXQ~WyI0Y2tnSjJuWVhhV21jM3pVQ253d3N3IiwgInllbGxvdyJd~WyJ2Ml8tRG5JN0lEZ1loYVMzTG9Kb013IiwgW3siLi4uIjogIkhqMUQtZE1SNXR0YmpLcl9DUENETzRuVGlkTWR1YVNpMnlnYlhtcmR4MGcifSwgeyIuLi4iOiAiUl9Sb253SFY3bzR6Y0o3TV9jcTlobVpLZ2o2RkMtdmNXTko4bzNkeTg2MCJ9XV0~WyJwRHQtcEtfaklUYXhCVENJRFNvUnhBIiwgIm9yYW5nZSJd~WyJ1b3FDS0lpZGJzQmxhczhUaU5Kakh3IiwgInB1cnBsZSJd~WyJJWWFXMzVPNzBoUWg4OGlqWVBUVXZRIiwgW3siLi4uIjogIjlWdnFSbjk1ZUN6QnVkNkhYOG1faVRNMERZSVVxN0ZheFFtanowV1llbUkifSwgeyIuLi4iOiAiTmFOeXozWEJRZVc4Z1JRd28ySlN5NmhtbnJZT1JxRjMxeUhfWkhqbkRxNCJ9XV0~WyI4cFNkNHl0TWlPdnVGaFhQSXBPbW5BIiwgImJsYWNrIl0~WyJxLWR0QXhtZzY5cWZLMFpvS1BSbWFnIiwgIndoaXRlIl0~WyIzTzY0WmVYSjF6XzJWMXdrMGhJdUdBIiwgW3siLi4uIjogImRmVnVjbkwwMC1FVFh0RGpHaDlpRHYtSE5PZmRyZ1VuTlNYRk01VUlIRVkifSwgeyIuLi4iOiAiUlRnVmxQb25RTVZJNkEzNUJic21KTThDeDVTVTN1ZXJBMENyYmpvRW02USJ9XV0~WyJNQTlSbGMwUlAxNnVJWER6blRqOWJ3IiwgIm5hbWUiLCAicHl0aG9uIl0~WyJrblRLb0lKVzZuQ1VzeW1sN3lKWTNBIiwgImFnZSIsIDEwXQ~WyJlUEdwazZjdEhOSS1HS2JKbjZrR3lBIiwgInNuYWtlIiwgeyJfc2QiOiBbIjREU0s5REpJVEhROElITFFESld6SV9yM0lheXBIek5Ma19tc3BUa2xDVzQiLCAiYy11UFhEQkZJX2FDV1BUUHlYNFV0OWdDWW1DQ1FqUEw5TnRFZGotdWZtMCJdfV0~WyJOMzgyX2xTU1dpSzZsbGdPNFFhbUdnIiwgIm5hbWUiLCAiZWFnbGUiXQ~WyJjVWZVRVBrX0pDZm1KQzhWQUp1V1pBIiwgImFnZSIsIDIwXQ~WyJSVDh5My1Odmh6QXo4Q2ctS1NDRGh3IiwgImJpcmQiLCB7Il9zZCI6IFsiUVhINU9mSF8tMGtFYkEwWDBnd0RLenphc05ZYWRWekNWRGFrYlZfWnNxNCIsICJoUmtPNjRIVXZuaEFPbDBRS1NlZDFUWUhtb0VpRW9zb0R0WmsyRVl4ejdNIl19XQ~");
591 let expected_presentation = String::from("eyJhbGciOiJFUzI1NiJ9.eyJmb28iOlt7Ii4uLiI6Ii1XMWROTk0tNUI3WlpxR3R4MkF6RTA3X0hpRUpOZVJtNGtEQ1VORTVDNFUifSx7Ii4uLiI6ImpuUURqUEFoclY1bjMtRW5PVEZHWTcwMkd0T3FhN3hua3pVM0E4aElSX3cifV0sImJhciI6eyJfc2QiOlsiX25yZUxad2xVYlp1SmtqS1RVdHR5YkhqUTNrY2J4cnZab1dxUmVBbG4tcyIsImhGcjdBRElQbjZvQ3lSckNBN0VtNldLaGk1UjdXMWJjYWFZUFFrelpGMXciXX0sInF1eCI6W3siLi4uIjoieHl6MkRSSDRTSkpjdFFtMDEtSzROVVllMTMzMWh6U3VkTXd3MENDODEyUSJ9XSwiYmF6IjpbeyIuLi4iOiJRMGcyVmYzNnl6TnNvUkdNb0dsODZnZ2QyWGFVTmg5bGN6STFfbmFZYUhnIn0seyIuLi4iOiJZcGpMNTJKd1BfYmFFS21OaHFLazE3TWFrMl9fSWJCNmctY0haSHd6dmwwIn1dLCJhbmltYWxzIjp7Il9zZCI6WyJyQ19LNzlObG95SkFPWXRCOW9ITFlsTVJSS1V4UTNnaTZ0Wld0Zm90TWRjIiwidjUyd3d6bzB5Ymw2U2V1MjZWYklUODh5bHk1LXVMZkdlYTdkWnMxSHBwMCJdfSwiX3NkX2FsZyI6InNoYS0yNTYifQ.piidRp0pHJYmtExCJnLExaaWMTBX50mLwM6gFVYnD72DszyjpKbAoZhyAXT-I4CqqSpiHZg-2w8s26XBraqX6w~WyJSLXZ0bDBmbWF6N01zR1ZWRFh3T3BnIiwgInR3byJd~WyJTQW5hNUJnaHJxUXJ0amR5SGxiejJBIiwgImdyZWVuIiwgMl0~WyI0Y2tnSjJuWVhhV21jM3pVQ253d3N3IiwgInllbGxvdyJd~WyJ2Ml8tRG5JN0lEZ1loYVMzTG9Kb013IiwgW3siLi4uIjogIkhqMUQtZE1SNXR0YmpLcl9DUENETzRuVGlkTWR1YVNpMnlnYlhtcmR4MGcifSwgeyIuLi4iOiAiUl9Sb253SFY3bzR6Y0o3TV9jcTlobVpLZ2o2RkMtdmNXTko4bzNkeTg2MCJ9XV0~WyJ1b3FDS0lpZGJzQmxhczhUaU5Kakh3IiwgInB1cnBsZSJd~WyJJWWFXMzVPNzBoUWg4OGlqWVBUVXZRIiwgW3siLi4uIjogIjlWdnFSbjk1ZUN6QnVkNkhYOG1faVRNMERZSVVxN0ZheFFtanowV1llbUkifSwgeyIuLi4iOiAiTmFOeXozWEJRZVc4Z1JRd28ySlN5NmhtbnJZT1JxRjMxeUhfWkhqbkRxNCJ9XV0~WyI4cFNkNHl0TWlPdnVGaFhQSXBPbW5BIiwgImJsYWNrIl0~WyJxLWR0QXhtZzY5cWZLMFpvS1BSbWFnIiwgIndoaXRlIl0~WyIzTzY0WmVYSjF6XzJWMXdrMGhJdUdBIiwgW3siLi4uIjogImRmVnVjbkwwMC1FVFh0RGpHaDlpRHYtSE5PZmRyZ1VuTlNYRk01VUlIRVkifSwgeyIuLi4iOiAiUlRnVmxQb25RTVZJNkEzNUJic21KTThDeDVTVTN1ZXJBMENyYmpvRW02USJ9XV0~WyJrblRLb0lKVzZuQ1VzeW1sN3lKWTNBIiwgImFnZSIsIDEwXQ~WyJlUEdwazZjdEhOSS1HS2JKbjZrR3lBIiwgInNuYWtlIiwgeyJfc2QiOiBbIjREU0s5REpJVEhROElITFFESld6SV9yM0lheXBIek5Ma19tc3BUa2xDVzQiLCAiYy11UFhEQkZJX2FDV1BUUHlYNFV0OWdDWW1DQ1FqUEw5TnRFZGotdWZtMCJdfV0~WyJjVWZVRVBrX0pDZm1KQzhWQUp1V1pBIiwgImFnZSIsIDIwXQ~WyJSVDh5My1Odmh6QXo4Q2ctS1NDRGh3IiwgImJpcmQiLCB7Il9zZCI6IFsiUVhINU9mSF8tMGtFYkEwWDBnd0RLenphc05ZYWRWekNWRGFrYlZfWnNxNCIsICJoUmtPNjRIVXZuaEFPbDBRS1NlZDFUWUhtb0VpRW9zb0R0WmsyRVl4ejdNIl19XQ~");
592
593 let revealed = json!(
595 {
596 "foo": [false, true],
597 "bar": {
598 "red": false,
599 "green": true
600 },
601 "qux": [
602 [false, true]
603 ],
604 "baz": [
605 [false, true],
606 [true, true]
607 ],
608 "animals": {
609 "snake": {
610 "name": false,
611 "age": true
612 },
613 "bird": {
614 "name": false,
615 "age": true
616 }
617 }
618 }
619 );
620
621 let presentation =
622 SDJWTHolder::new(sd_jwt, SDJWTSerializationFormat::Compact)
623 .unwrap()
624 .create_presentation(
625 revealed.as_object().unwrap().clone(),
626 None,
627 None,
628 None,
629 None,
630 )
631 .unwrap();
632
633 let presentation: HashSet<_> = presentation
634 .split(COMBINED_SERIALIZATION_FORMAT_SEPARATOR).map(String::from)
635 .collect();
636
637 let expected: HashSet<_> = expected_presentation
638 .split(COMBINED_SERIALIZATION_FORMAT_SEPARATOR)
639 .map(String::from).collect();
640
641 assert_eq!(presentation, expected);
642 }
643}