1use std::fmt::Debug;
5use std::fmt::Display;
6use std::hash::Hash;
7use std::str::FromStr;
8
9use identity_jose::jwk::Jwk;
10use identity_jose::jwu::decode_b64_json;
11use identity_jose::jwu::encode_b64_json;
12
13use crate::CoreDID;
14use crate::Error;
15use crate::DID;
16
17#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
18#[serde(into = "CoreDID", try_from = "CoreDID")]
19pub struct DIDJwk {
21 did: CoreDID,
22 jwk: Jwk,
23}
24
25impl DIDJwk {
26 pub const METHOD: &'static str = "jwk";
28
29 pub fn new(jwk: impl Into<Jwk>) -> Self {
31 let jwk = jwk.into();
32 let did_str = format!("did:jwk:{}", encode_b64_json(&jwk).expect("valid JSON"));
33 let did = did_str.parse().expect("valid CoreDID");
34
35 Self { did, jwk }
36 }
37
38 pub fn parse(s: &str) -> Result<Self, Error> {
40 s.parse()
41 }
42
43 pub fn jwk(&self) -> Jwk {
45 self.jwk.clone()
46 }
47
48 pub fn as_jwk(&self) -> &Jwk {
50 &self.jwk
51 }
52}
53
54impl Ord for DIDJwk {
55 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
56 self.did.cmp(&other.did)
57 }
58}
59
60impl PartialOrd for DIDJwk {
61 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
62 Some(self.cmp(other))
63 }
64}
65
66impl Hash for DIDJwk {
67 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
68 self.did.hash(state)
69 }
70}
71
72impl AsRef<CoreDID> for DIDJwk {
73 fn as_ref(&self) -> &CoreDID {
74 &self.did
75 }
76}
77
78impl AsRef<Jwk> for DIDJwk {
79 fn as_ref(&self) -> &Jwk {
80 &self.jwk
81 }
82}
83
84impl From<DIDJwk> for CoreDID {
85 fn from(value: DIDJwk) -> Self {
86 value.did
87 }
88}
89
90impl<'a> TryFrom<&'a str> for DIDJwk {
91 type Error = Error;
92 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
93 value.parse()
94 }
95}
96
97impl Display for DIDJwk {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 write!(f, "{}", self.did)
100 }
101}
102
103impl FromStr for DIDJwk {
104 type Err = Error;
105 fn from_str(s: &str) -> Result<Self, Self::Err> {
106 s.parse::<CoreDID>().and_then(TryFrom::try_from)
107 }
108}
109
110impl From<DIDJwk> for String {
111 fn from(value: DIDJwk) -> Self {
112 value.to_string()
113 }
114}
115
116impl From<DIDJwk> for Jwk {
117 fn from(value: DIDJwk) -> Self {
118 value.jwk
119 }
120}
121
122impl TryFrom<CoreDID> for DIDJwk {
123 type Error = Error;
124 fn try_from(value: CoreDID) -> Result<Self, Self::Error> {
125 let Self::METHOD = value.method() else {
126 return Err(Error::InvalidMethodName);
127 };
128 decode_b64_json(value.method_id())
129 .map(|jwk| Self { did: value, jwk })
130 .map_err(|_| Error::InvalidMethodId)
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use identity_core::convert::FromJson;
137
138 use super::*;
139
140 #[test]
141 fn test_valid_deserialization() -> Result<(), Error> {
142 "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9".parse::<DIDJwk>()?;
143 "did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9".parse::<DIDJwk>()?;
144
145 Ok(())
146 }
147
148 #[test]
149 fn test_jwk() {
150 let did = DIDJwk::parse("did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9").unwrap();
151 let target_jwk = Jwk::from_json_value(serde_json::json!({
152 "kty":"OKP","crv":"X25519","use":"enc","x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"
153 }))
154 .unwrap();
155
156 assert_eq!(did.jwk(), target_jwk);
157 }
158
159 #[test]
160 fn test_new() {
161 let jwk = Jwk::from_json_value(serde_json::json!({
162 "kty":"OKP","crv":"X25519","use":"enc","x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"
163 }))
164 .unwrap();
165 let target_did_jwk = DIDJwk::parse(&format!("did:jwk:{}", encode_b64_json(&jwk).unwrap())).unwrap();
166
167 let did_jwk = DIDJwk::new(jwk);
168 assert_eq!(target_did_jwk, did_jwk);
169 }
170
171 #[test]
172 fn test_invalid_deserialization() {
173 assert!(
174 "did:iota:0xf4d6f08f5a1b80dd578da7dc1b49c886d580acd4cf7d48119dfeb82b538ad88a"
175 .parse::<DIDJwk>()
176 .is_err()
177 );
178 assert!("did:jwk:".parse::<DIDJwk>().is_err());
179 assert!("did:jwk:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp"
180 .parse::<DIDJwk>()
181 .is_err());
182 }
183}