1use serde::{Deserialize, Serialize};
16
17use crate::{
18 encoding::{
19 base64url_decode, base64url_encode, base64url_encode_serializable, SerializationType,
20 },
21 errors::CustomError,
22 jpa::{algs::PresentationProofAlgorithm, bbs_plus::BBSplusAlgorithm},
23 jpt::{
24 claims::Claims,
25 payloads::{PayloadType, Payloads},
26 },
27 jwk::key::Jwk,
28};
29
30use super::{
31 header::{IssuerProtectedHeader, PresentationProtectedHeader},
32 issued::JwpIssued,
33};
34
35macro_rules! expect_four {
38 ($iter:expr) => {{
39 let mut i = $iter;
40 match (i.next(), i.next(), i.next(), i.next()) {
41 (Some(first), Some(second), Some(third), Some(fourth)) => {
42 (first, second, third, fourth)
43 }
44 _ => return Err(CustomError::InvalidPresentedJwp),
45 }
46 }};
47}
48
49#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
51pub struct JwpPresentedBuilder {
52 issuer_protected_header: IssuerProtectedHeader,
53 presentation_protected_header: Option<PresentationProtectedHeader>,
54 payloads: Payloads,
55 issuer_proof: Vec<u8>,
56}
57
58impl JwpPresentedBuilder {
59 pub fn new(issued_jwp: &JwpIssued) -> Self {
60 Self {
61 issuer_protected_header: issued_jwp.get_issuer_protected_header().clone(),
62 presentation_protected_header: None,
63 payloads: issued_jwp.get_payloads().clone(),
64 issuer_proof: issued_jwp.get_proof().to_vec(),
65 }
66 }
67
68 pub fn set_presentation_protected_header(
69 &mut self,
70 header: PresentationProtectedHeader,
71 ) -> &mut Self {
72 self.presentation_protected_header = Some(header);
73 self
74 }
75
76 pub fn get_issuer_protected_header(&self) -> &IssuerProtectedHeader {
78 &self.issuer_protected_header
79 }
80
81 pub fn get_presentation_protected_header(&self) -> Option<&PresentationProtectedHeader> {
83 self.presentation_protected_header.as_ref()
84 }
85
86 pub fn get_payloads(&self) -> &Payloads {
88 &self.payloads
89 }
90
91 pub fn issuer_proof(&self) -> &Vec<u8> {
93 &self.issuer_proof
94 }
95
96 pub fn set_undisclosed(&mut self, claim: &str) -> Result<&mut Self, CustomError> {
97 let index = self
98 .issuer_protected_header
99 .claims()
100 .and_then(|c| c.0.iter().position(|x| x == claim))
101 .ok_or(CustomError::SelectiveDisclosureError)?;
102 self.payloads.set_undisclosed(index);
103 Ok(self)
104 }
105
106 pub fn build_with_proof(&self, proof: Vec<u8>) -> Result<JwpPresented, CustomError> {
107 if let Some(presentation_protected_header) = self.presentation_protected_header.clone() {
108 Ok(JwpPresented {
109 issuer_protected_header: self.issuer_protected_header.clone(),
110 presentation_protected_header,
111 payloads: self.payloads.clone(),
112 proof,
113 })
114 } else {
115 Err(CustomError::IncompleteJwpBuild(
116 crate::errors::IncompleteJwpBuild::NoIssuerHeader,
117 ))
118 }
119 }
120
121 pub fn build(&self, jwk: &Jwk) -> Result<JwpPresented, CustomError> {
122 if let Some(presentation_protected_header) = self.presentation_protected_header.clone() {
123 let issuer_header_oct = serde_json::to_vec(&self.issuer_protected_header).unwrap();
124 let presentation_header_oct =
125 serde_json::to_vec(&self.presentation_protected_header).unwrap();
126
127 let proof = Self::generate_proof(
128 presentation_protected_header.alg(),
129 jwk,
130 &self.issuer_proof,
131 &issuer_header_oct,
132 &presentation_header_oct,
133 &self.payloads,
134 )?;
135 Ok(JwpPresented {
136 issuer_protected_header: self.issuer_protected_header.clone(),
137 presentation_protected_header,
138 payloads: self.payloads.clone(),
139 proof,
140 })
141 } else {
142 Err(CustomError::IncompleteJwpBuild(
143 crate::errors::IncompleteJwpBuild::NoIssuerHeader,
144 ))
145 }
146 }
147
148 fn generate_proof(
149 alg: PresentationProofAlgorithm,
150 key: &Jwk,
151 issuer_proof: &[u8],
152 issuer_header_oct: &[u8],
153 presentation_header_oct: &[u8],
154 payloads: &Payloads,
155 ) -> Result<Vec<u8>, CustomError> {
156 let proof = match alg {
157 PresentationProofAlgorithm::BBS
158 | PresentationProofAlgorithm::BBS_SHAKE256 => {
159 BBSplusAlgorithm::generate_presentation_proof(
160 alg,
161 issuer_proof,
162 payloads,
163 key,
164 issuer_header_oct,
165 presentation_header_oct,
166 )?
167 }
168 PresentationProofAlgorithm::SU_ES256 => todo!(),
169 PresentationProofAlgorithm::SU_ES384 => todo!(),
170 PresentationProofAlgorithm::SU_ES512 => todo!(),
171 PresentationProofAlgorithm::MAC_H256 => todo!(),
172 PresentationProofAlgorithm::MAC_H384 => todo!(),
173 PresentationProofAlgorithm::MAC_H512 => todo!(),
174 PresentationProofAlgorithm::MAC_K25519 => todo!(),
175 PresentationProofAlgorithm::MAC_K448 => todo!(),
176 PresentationProofAlgorithm::MAC_H256K => todo!(),
177 };
178
179 Ok(proof)
180 }
181}
182
183#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
185pub struct JwpPresentedDecoder {
186 issuer_protected_header: IssuerProtectedHeader,
187 presentation_protected_header: PresentationProtectedHeader,
188 payloads: Payloads,
189 proof: Vec<u8>,
190}
191
192impl JwpPresentedDecoder {
193 pub fn decode(jpt: &str, serialization: SerializationType) -> Result<Self, CustomError> {
195 match serialization {
196 SerializationType::COMPACT => {
197 let (
198 encoded_issuer_protected_header,
199 encoded_presentation_protected_header,
200 encoded_payloads,
201 encoded_proof,
202 ) = expect_four!(jpt.splitn(4, '.'));
203 let presentation_protected_header: PresentationProtectedHeader =
204 serde_json::from_slice(&base64url_decode(
205 encoded_presentation_protected_header,
206 ))
207 .map_err(|_| CustomError::SerializationError)?;
208 let issuer_protected_header: IssuerProtectedHeader =
209 serde_json::from_slice(&base64url_decode(encoded_issuer_protected_header))
210 .map_err(|_| CustomError::SerializationError)?;
211 let payloads = Payloads(
212 encoded_payloads
213 .splitn(issuer_protected_header.claims().unwrap().0.len(), "~")
214 .map(|v| {
215 if v == "" {
216 (serde_json::Value::Null, PayloadType::Undisclosed)
217 } else {
218 (
219 serde_json::from_slice(&base64url_decode(v)).unwrap(),
220 PayloadType::Disclosed,
221 )
222 }
223 })
224 .collect(),
225 );
226
227 if !match issuer_protected_header.claims() {
228 Some(claims) => claims.0.len() == payloads.0.len(),
229 None => payloads.0.len() == 0,
230 } {
231 return Err(CustomError::InvalidIssuedJwp);
232 }
233
234 let proof = base64url_decode(encoded_proof);
235
236 Ok(Self {
237 issuer_protected_header,
238 payloads,
239 proof: proof,
240 presentation_protected_header,
241 })
242 }
243 SerializationType::JSON => todo!(),
244 SerializationType::CBOR => todo!(),
245 }
246 }
247
248 pub fn verify(&self, key: &Jwk) -> Result<JwpPresented, CustomError> {
250 let issuer_header_oct = serde_json::to_vec(&self.issuer_protected_header).unwrap();
251 let presentation_header_oct =
252 serde_json::to_vec(&self.presentation_protected_header).unwrap();
253 Self::verify_proof(
254 self.presentation_protected_header.alg(),
255 key,
256 &self.proof,
257 &presentation_header_oct,
258 &issuer_header_oct,
259 &self.payloads,
260 )?;
261 Ok(JwpPresented {
262 issuer_protected_header: self.issuer_protected_header.clone(),
263 presentation_protected_header: self.presentation_protected_header.clone(),
264 payloads: self.payloads.clone(),
265 proof: self.proof.clone(),
266 })
267 }
268
269 pub fn get_issuer_header(&self) -> &IssuerProtectedHeader {
270 &self.issuer_protected_header
271 }
272
273 pub fn get_presentation_header(&self) -> &PresentationProtectedHeader {
274 &self.presentation_protected_header
275 }
276
277 pub fn get_payloads(&self) -> &Payloads {
278 &self.payloads
279 }
280
281 fn verify_proof(
282 alg: PresentationProofAlgorithm,
283 key: &Jwk,
284 proof: &[u8],
285 presentation_header_oct: &[u8],
286 issuer_header_oct: &[u8],
287 payloads: &Payloads,
288 ) -> Result<(), CustomError> {
289 let check = match alg {
290 PresentationProofAlgorithm::BBS
291 | PresentationProofAlgorithm::BBS_SHAKE256 => {
292 BBSplusAlgorithm::verify_presentation_proof(
293 alg,
294 &key,
295 proof,
296 presentation_header_oct,
297 issuer_header_oct,
298 payloads,
299 )
300 }
301 PresentationProofAlgorithm::SU_ES256 => todo!(),
302 PresentationProofAlgorithm::SU_ES384 => todo!(),
303 PresentationProofAlgorithm::SU_ES512 => todo!(),
304 PresentationProofAlgorithm::MAC_H256 => todo!(),
305 PresentationProofAlgorithm::MAC_H384 => todo!(),
306 PresentationProofAlgorithm::MAC_H512 => todo!(),
307 PresentationProofAlgorithm::MAC_K25519 => todo!(),
308 PresentationProofAlgorithm::MAC_K448 => todo!(),
309 PresentationProofAlgorithm::MAC_H256K => todo!(),
310 };
311
312 check
313 }
314}
315
316#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
318pub struct JwpPresented {
319 issuer_protected_header: IssuerProtectedHeader,
320 presentation_protected_header: PresentationProtectedHeader,
321 payloads: Payloads,
322 proof: Vec<u8>,
323}
324
325impl JwpPresented {
326 pub fn encode(&self, serialization: SerializationType) -> Result<String, CustomError> {
328 let issuer_header_oct = serde_json::to_vec(&self.issuer_protected_header)
332 .map_err(|_| CustomError::SerializationError)?;
333
334 let presentation_header_oct = serde_json::to_vec(&self.presentation_protected_header)
335 .map_err(|_| CustomError::SerializationError)?;
336
337 let jwp = Self::serialize(
338 serialization,
339 &presentation_header_oct,
340 &issuer_header_oct,
341 &self.payloads,
342 &self.proof,
343 );
344
345 Ok(jwp)
346 }
347
348 pub fn get_issuer_protected_header(&self) -> &IssuerProtectedHeader {
349 &self.issuer_protected_header
350 }
351
352 pub fn get_presentation_protected_header(&self) -> &PresentationProtectedHeader {
353 &self.presentation_protected_header
354 }
355
356 pub fn get_claims(&self) -> Option<&Claims> {
357 self.issuer_protected_header.claims()
358 }
359
360 pub fn get_payloads(&self) -> &Payloads {
361 &self.payloads
362 }
363
364 pub fn get_proof(&self) -> &[u8] {
365 &self.proof
366 }
367
368 fn serialize(
369 serialization: SerializationType,
370 presentation_header_oct: &[u8],
371 issuer_header_oct: &[u8],
372 payloads: &Payloads,
373 proof: &[u8],
374 ) -> String {
375 let encoded_issuer_header = base64url_encode(issuer_header_oct);
376 let encoded_presentation_header = base64url_encode(presentation_header_oct);
377 let encoded_proof = base64url_encode(proof);
378
379 let jwp = match serialization {
380 SerializationType::COMPACT => {
381 let encoded_payloads = payloads
382 .0
383 .iter()
384 .map(|p| {
385 if p.1 == PayloadType::Undisclosed {
386 "".to_string()
387 } else {
388 base64url_encode_serializable(&p.0)
389 }
390 })
391 .collect::<Vec<String>>()
392 .join("~");
393 format!(
394 "{}.{}.{}.{}",
395 encoded_issuer_header,
396 encoded_presentation_header,
397 encoded_payloads,
398 encoded_proof
399 )
400 }
401 SerializationType::JSON => todo!(),
402 SerializationType::CBOR => todo!(),
403 };
404
405 jwp
406 }
407}
408
409
410#[cfg(test)]
411mod tests {
412
413 use crate::{
414 encoding::SerializationType,
415 jpa::algs::{PresentationProofAlgorithm, ProofAlgorithm},
416 jpt::claims::JptClaims,
417 jwk::{key::Jwk, types::KeyPairSubtype},
418 jwp::{
419 header::{IssuerProtectedHeader, PresentationProtectedHeader},
420 issued::{JwpIssuedBuilder, JwpIssuedDecoder},
421 presented::{JwpPresentedBuilder, JwpPresentedDecoder},
422 },
423 };
424
425 #[test]
426 fn test_jwp_presented(){
427 let custom_claims = serde_json::json!({
428 "degree": {
429 "type": "BachelorDegree",
430 "name": "Bachelor of Science and Arts",
431 },
432 "name": "John Doe"
433 });
434
435 let mut jpt_claims = JptClaims::new();
436 jpt_claims.set_iss("https://issuer.example".to_owned());
437 jpt_claims.set_claim(Some("vc"), custom_claims, true);
438
439 let issued_header = IssuerProtectedHeader::new(ProofAlgorithm::BBS);
440
441 let bbs_jwk = Jwk::generate(KeyPairSubtype::BLS12381G2Sha256).unwrap();
442
443 let issued_jwp = JwpIssuedBuilder::new(issued_header, jpt_claims)
444 .build(&bbs_jwk)
445 .unwrap();
446
447 let compact_issued_jwp = issued_jwp.encode(SerializationType::COMPACT).unwrap();
448
449 let decoded_issued_jwp =
450 JwpIssuedDecoder::decode(&compact_issued_jwp, SerializationType::COMPACT)
451 .unwrap()
452 .verify(&bbs_jwk.to_public().unwrap())
453 .unwrap();
454
455 let mut presentation_header = PresentationProtectedHeader::new(PresentationProofAlgorithm::BBS);
456 presentation_header.set_aud(Some("https://recipient.example.com".to_owned()));
457 presentation_header.set_nonce(Some("wrmBRkKtXjQ".to_owned()));
458
459 let presented_jwp = JwpPresentedBuilder::new(&decoded_issued_jwp)
460 .set_presentation_protected_header(presentation_header)
461 .build(&bbs_jwk.to_public().unwrap())
462 .unwrap();
463
464 let compact_presented_jwp = presented_jwp.encode(SerializationType::COMPACT).unwrap();
465
466 let decoded_presented_jwp =
467 JwpPresentedDecoder::decode(&compact_presented_jwp, SerializationType::COMPACT)
468 .unwrap()
469 .verify(&bbs_jwk.to_public().unwrap())
470 .unwrap();
471
472 assert_eq!(presented_jwp, decoded_presented_jwp);
473
474 }
475
476 #[test]
477 fn test_jwp_presented_selective_disclosure(){
478 let custom_claims = serde_json::json!({
479 "degree": {
480 "type": "BachelorDegree",
481 "name": "Bachelor of Science and Arts",
482 "ciao": [
483 {"u1": "value1"},
484 {"u2": "value2"}
485 ]
486 },
487 "name": "John Doe"
488 });
489
490 let mut jpt_claims = JptClaims::new();
491 jpt_claims.set_iss("https://issuer.example".to_owned());
492 jpt_claims.set_claim(Some("vc"), custom_claims, true);
493
494 let issued_header = IssuerProtectedHeader::new(ProofAlgorithm::BBS);
495
496 let bbs_jwk = Jwk::generate(KeyPairSubtype::BLS12381G2Sha256).unwrap();
497
498 let issued_jwp = JwpIssuedBuilder::new(issued_header, jpt_claims)
499 .build(&bbs_jwk)
500 .unwrap();
501
502 let compact_issued_jwp = issued_jwp.encode(SerializationType::COMPACT).unwrap();
503
504 let decoded_issued_jwp =
505 JwpIssuedDecoder::decode(&compact_issued_jwp, SerializationType::COMPACT)
506 .unwrap()
507 .verify(&bbs_jwk.to_public().unwrap())
508 .unwrap();
509
510 let mut presentation_header = PresentationProtectedHeader::new(
511 decoded_issued_jwp
512 .get_issuer_protected_header()
513 .alg()
514 .into(),
515 );
516 presentation_header.set_aud(Some("https://recipient.example.com".to_owned()));
517 presentation_header.set_nonce(Some("wrmBRkKtXjQ".to_owned()));
518
519 let presented_jwp = JwpPresentedBuilder::new(&decoded_issued_jwp)
520 .set_presentation_protected_header(presentation_header)
521 .set_undisclosed("vc.degree.name")
522 .unwrap()
523 .set_undisclosed("vc.degree.ciao[0].u1")
524 .unwrap()
525 .set_undisclosed("vc.name")
526 .unwrap()
527 .build(&bbs_jwk.to_public().unwrap())
528 .unwrap();
529
530 let presented_claims = presented_jwp.get_claims().unwrap();
531
532 assert!(presented_claims.0.contains(&"vc.degree.name".to_owned()));
533 assert!(presented_claims.0.contains(&"vc.degree.ciao[0].u1".to_owned()));
534 assert!(presented_claims.0.contains(&"vc.name".to_owned()));
535 assert!(!presented_claims.0.contains(&"vc.degree.ciao[0].u2".to_owned()));
536
537 }
538
539}