1pub mod disclosure;
14pub mod issuer;
15pub mod lifecycle;
16pub mod verifier;
17
18use baseid_core::error::CryptoError;
19use serde::{Deserialize, Serialize};
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct SdJwt {
24 pub jwt: String,
26 pub disclosures: Vec<String>,
28 pub key_binding_jwt: Option<String>,
30}
31
32impl SdJwt {
33 pub fn serialize(&self) -> String {
38 let mut parts = vec![self.jwt.clone()];
39 for d in &self.disclosures {
40 parts.push(d.clone());
41 }
42 match &self.key_binding_jwt {
44 Some(kb) => {
45 let mut s = parts.join("~");
46 s.push('~');
47 s.push_str(kb);
48 s
49 }
50 None => {
51 let mut s = parts.join("~");
52 s.push('~');
53 s
54 }
55 }
56 }
57
58 pub fn parse(compact: &str) -> baseid_core::Result<Self> {
60 let parts: Vec<&str> = compact.split('~').collect();
63 if parts.len() < 2 {
64 return Err(CryptoError::VerificationFailed.into());
65 }
66
67 let jwt = parts[0].to_string();
68 if jwt.is_empty() {
69 return Err(CryptoError::VerificationFailed.into());
70 }
71
72 let last = parts[parts.len() - 1];
74 let (disclosures_slice, key_binding_jwt) = if last.is_empty() {
75 (&parts[1..parts.len() - 1], None)
76 } else {
77 (&parts[1..parts.len() - 1], Some(last.to_string()))
78 };
79
80 let disclosures: Vec<String> = disclosures_slice
81 .iter()
82 .filter(|s| !s.is_empty())
83 .map(|s| s.to_string())
84 .collect();
85
86 Ok(Self {
87 jwt,
88 disclosures,
89 key_binding_jwt,
90 })
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn serialize_parse_roundtrip() {
100 let sd = SdJwt {
101 jwt: "eyJ0eXAiOiJKV1QifQ.eyJpc3MiOiJ0ZXN0In0.sig".to_string(),
102 disclosures: vec!["disc1".to_string(), "disc2".to_string()],
103 key_binding_jwt: None,
104 };
105 let compact = sd.serialize();
106 assert!(compact.ends_with('~'));
107
108 let parsed = SdJwt::parse(&compact).unwrap();
109 assert_eq!(parsed.jwt, sd.jwt);
110 assert_eq!(parsed.disclosures, sd.disclosures);
111 assert!(parsed.key_binding_jwt.is_none());
112 }
113
114 #[test]
115 fn serialize_parse_with_kb_jwt() {
116 let sd = SdJwt {
117 jwt: "jwt_part".to_string(),
118 disclosures: vec!["d1".to_string()],
119 key_binding_jwt: Some("kb_jwt".to_string()),
120 };
121 let compact = sd.serialize();
122 assert!(!compact.ends_with('~'));
123
124 let parsed = SdJwt::parse(&compact).unwrap();
125 assert_eq!(parsed.jwt, "jwt_part");
126 assert_eq!(parsed.disclosures, vec!["d1"]);
127 assert_eq!(parsed.key_binding_jwt, Some("kb_jwt".to_string()));
128 }
129
130 #[test]
131 fn serialize_no_disclosures() {
132 let sd = SdJwt {
133 jwt: "jwt".to_string(),
134 disclosures: vec![],
135 key_binding_jwt: None,
136 };
137 let compact = sd.serialize();
138 assert_eq!(compact, "jwt~");
139
140 let parsed = SdJwt::parse(&compact).unwrap();
141 assert_eq!(parsed.jwt, "jwt");
142 assert!(parsed.disclosures.is_empty());
143 }
144}