identity_did/
did_jwk.rs

1// Copyright 2020-2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use 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")]
19/// A type representing a `did:jwk` DID.
20pub struct DIDJwk {
21  did: CoreDID,
22  jwk: Jwk,
23}
24
25impl DIDJwk {
26  /// [`DIDJwk`]'s method.
27  pub const METHOD: &'static str = "jwk";
28
29  /// Creates a new [DIDJwk] from the given [Jwk].
30  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  /// Tries to parse a [`DIDJwk`] from a string.
39  pub fn parse(s: &str) -> Result<Self, Error> {
40    s.parse()
41  }
42
43  /// Returns the JWK encoded inside this did:jwk.
44  pub fn jwk(&self) -> Jwk {
45    self.jwk.clone()
46  }
47
48  /// Returns a reference to the [Jwk] encoded inside this did:jwk.
49  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}