identity_verification/verification_method/
method.rs

1// Copyright 2020-2025 IOTA Stiftung, Fondazione LINKS
2// SPDX-License-Identifier: Apache-2.0
3
4use core::fmt::Display;
5use core::fmt::Formatter;
6use std::borrow::Cow;
7
8use identity_did::DIDCompositeJwk;
9use identity_did::DIDJwk;
10use identity_jose::jwk::CompositeJwk;
11use identity_jose::jwk::Jwk;
12use serde::de;
13use serde::Deserialize;
14use serde::Serialize;
15
16use identity_core::common::KeyComparable;
17use identity_core::common::Object;
18use identity_core::convert::FmtJson;
19
20use crate::error::Error;
21use crate::error::Result;
22use crate::verification_method::MethodBuilder;
23use crate::verification_method::MethodData;
24use crate::verification_method::MethodRef;
25use crate::verification_method::MethodType;
26use crate::CustomMethodData;
27use identity_did::CoreDID;
28use identity_did::DIDUrl;
29use identity_did::DID;
30
31/// A DID Document Verification Method.
32///
33/// [Specification](https://www.w3.org/TR/did-core/#verification-method-properties)
34#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
35#[serde(from = "_VerificationMethod")]
36pub struct VerificationMethod {
37  pub(crate) id: DIDUrl,
38  pub(crate) controller: CoreDID,
39  #[serde(rename = "type")]
40  pub(crate) type_: MethodType,
41  #[serde(flatten)]
42  pub(crate) data: MethodData,
43  #[serde(flatten)]
44  pub(crate) properties: Object,
45}
46
47/// Deserializes an [`DIDUrl`] while enforcing that its fragment is non-empty.
48fn deserialize_id_with_fragment<'de, D>(deserializer: D) -> Result<DIDUrl, D::Error>
49where
50  D: de::Deserializer<'de>,
51{
52  let did_url: DIDUrl = DIDUrl::deserialize(deserializer)?;
53  if did_url.fragment().unwrap_or_default().is_empty() {
54    return Err(de::Error::custom("method id missing fragment"));
55  }
56  Ok(did_url)
57}
58
59impl VerificationMethod {
60  // ===========================================================================
61  // Builder
62  // ===========================================================================
63
64  /// Creates a `MethodBuilder` to configure a new `Method`.
65  ///
66  /// This is the same as `MethodBuilder::new()`.
67  pub fn builder(properties: Object) -> MethodBuilder {
68    MethodBuilder::new(properties)
69  }
70
71  /// Returns a new `Method` based on the `MethodBuilder` configuration.
72  pub fn from_builder(builder: MethodBuilder) -> Result<Self> {
73    let id: DIDUrl = builder.id.ok_or(Error::InvalidMethod("missing id"))?;
74    if id.fragment().unwrap_or_default().is_empty() {
75      return Err(Error::InvalidMethod("empty id fragment"));
76    }
77
78    if let Some(MethodData::PublicKeyJwk(ref jwk)) = builder.data {
79      if !jwk.is_public() {
80        return Err(crate::error::Error::PrivateKeyMaterialExposed);
81      }
82    };
83
84    Ok(VerificationMethod {
85      id,
86      controller: builder.controller.ok_or(Error::InvalidMethod("missing controller"))?,
87      type_: builder.type_.ok_or(Error::InvalidMethod("missing type"))?,
88      data: builder.data.ok_or(Error::InvalidMethod("missing data"))?,
89      properties: builder.properties,
90    })
91  }
92
93  // ===========================================================================
94  // Properties
95  // ===========================================================================
96
97  /// Returns a reference to the `VerificationMethod` id.
98  pub fn id(&self) -> &DIDUrl {
99    &self.id
100  }
101
102  /// Sets the `VerificationMethod` id.
103  ///
104  /// # Errors
105  /// [`Error::MissingIdFragment`] if there is no fragment on the [`DIDUrl`].
106  pub fn set_id(&mut self, id: DIDUrl) -> Result<()> {
107    if id.fragment().unwrap_or_default().is_empty() {
108      return Err(Error::MissingIdFragment);
109    }
110    self.id = id;
111    Ok(())
112  }
113
114  /// Returns a reference to the `VerificationMethod` controller.
115  pub fn controller(&self) -> &CoreDID {
116    &self.controller
117  }
118
119  /// Returns a mutable reference to the `VerificationMethod` controller.
120  pub fn controller_mut(&mut self) -> &mut CoreDID {
121    &mut self.controller
122  }
123
124  /// Returns a reference to the `VerificationMethod` type.
125  pub fn type_(&self) -> &MethodType {
126    &self.type_
127  }
128
129  /// Returns a mutable reference to the `VerificationMethod` type.
130  pub fn type_mut(&mut self) -> &mut MethodType {
131    &mut self.type_
132  }
133
134  /// Returns a reference to the `VerificationMethod` data.
135  pub fn data(&self) -> &MethodData {
136    &self.data
137  }
138
139  /// Returns a mutable reference to the `VerificationMethod` data.
140  pub fn data_mut(&mut self) -> &mut MethodData {
141    &mut self.data
142  }
143
144  /// Returns a reference to the custom `VerificationMethod` properties.
145  pub fn properties(&self) -> &Object {
146    &self.properties
147  }
148
149  /// Returns a mutable reference to the custom `VerificationMethod` properties.
150  pub fn properties_mut(&mut self) -> &mut Object {
151    &mut self.properties
152  }
153
154  /// Creates a new [`MethodRef`] from `self`.
155  pub fn into_method_ref(self) -> MethodRef {
156    MethodRef::Embed(self)
157  }
158
159  /// Maps the [`VerificationMethod`] by applying a function `f` to
160  /// the [`CoreDID`] components of id and controller. Useful when working with DID methods where the identifier
161  /// is not known before publishing.
162  pub fn map<F>(self, mut f: F) -> VerificationMethod
163  where
164    F: FnMut(CoreDID) -> CoreDID,
165  {
166    VerificationMethod {
167      id: self.id.map(&mut f),
168      controller: f(self.controller),
169      type_: self.type_,
170      data: self.data,
171      properties: self.properties,
172    }
173  }
174
175  /// Fallible version of [`VerificationMethod::map`].
176  pub fn try_map<F, E>(self, mut f: F) -> Result<VerificationMethod, E>
177  where
178    F: FnMut(CoreDID) -> Result<CoreDID, E>,
179  {
180    Ok(VerificationMethod {
181      id: self.id.try_map(&mut f)?,
182      controller: f(self.controller)?,
183      type_: self.type_,
184      data: self.data,
185      properties: self.properties,
186    })
187  }
188}
189
190impl VerificationMethod {
191  // ===========================================================================
192  // Constructors
193  // ===========================================================================
194
195  /// Creates a new [`VerificationMethod`] from the given `did` and [`Jwk`]. If `fragment` is not given
196  /// the `kid` value of the given `key` will be used, if present, otherwise an error is returned.
197  ///
198  /// # Recommendations
199  /// The following recommendations are essentially taken from the `publicKeyJwk` description from
200  /// the [DID specification](https://www.w3.org/TR/did-core/#dfn-publickeyjwk):
201  /// - It is recommended that verification methods that use [`Jwks`](Jwk) to represent their public keys use the value
202  ///   of `kid` as their fragment identifier. This is done automatically if `None` is passed in as the fragment.
203  /// - It is recommended that [`Jwk`] kid values are set to the public key fingerprint. See
204  ///   [`Jwk::thumbprint_sha256_b64`](Jwk::thumbprint_sha256_b64).
205  pub fn new_from_jwk<D: DID>(did: D, key: Jwk, fragment: Option<&str>) -> Result<Self> {
206    // If a fragment is given use that, otherwise use the JWK's `kid` if it is set.
207    let fragment: Cow<'_, str> = {
208      let given_fragment: &str = fragment
209        .or_else(|| key.kid())
210        .ok_or(crate::error::Error::InvalidMethod(
211          "an explicit fragment or JWK kid is required",
212        ))?;
213      // Make sure the fragment starts with "#"
214      if given_fragment.starts_with('#') {
215        Cow::Borrowed(given_fragment)
216      } else {
217        Cow::Owned(format!("#{given_fragment}"))
218      }
219    };
220
221    let id: DIDUrl = did.to_url().join(fragment).map_err(Error::DIDUrlConstructionError)?;
222
223    MethodBuilder::default()
224      .id(id)
225      .controller(did.into())
226      .type_(MethodType::JSON_WEB_KEY_2020)
227      .data(MethodData::PublicKeyJwk(key))
228      .build()
229  }
230}
231
232impl VerificationMethod {
233  // ===========================================================================
234  // Constructors
235  // ===========================================================================
236
237  /// Creates a new [`VerificationMethod`] from the given `did` and [`CompositeJwk`]. If `fragment` is not given
238  /// the `kid` value of the given `key` will be used, if present, otherwise an error is returned.
239  pub fn new_from_compositejwk<D: DID>(did: D, key: CompositeJwk, fragment: Option<&str>) -> Result<Self> {
240    // Lazily compute the composite fragment.
241    let composite_fragment = || {
242      let maybe_tpk_kid = key.traditional_public_key().kid();
243      let maybe_pqpk_kid = key.pq_public_key().kid();
244      if let (Some(tpk_kid), Some(pqpk_kid)) = (maybe_tpk_kid, maybe_pqpk_kid) {
245        Some(Cow::Owned(format!("{tpk_kid}~{pqpk_kid}")))
246      } else {
247        maybe_tpk_kid.or(maybe_pqpk_kid).map(Cow::Borrowed)
248      }
249    };
250
251    let fragment = {
252      let given_fragment = fragment
253        .map(Cow::Borrowed)
254        .or_else(composite_fragment)
255        .ok_or(Error::InvalidMethod("an explicit fragment or kid is required"))?;
256
257      // Make sure the fragment starts with "#"
258      if given_fragment.starts_with('#') {
259        given_fragment
260      } else {
261        Cow::Owned(format!("#{given_fragment}"))
262      }
263    };
264
265    let id: DIDUrl = did.to_url().join(fragment).map_err(Error::DIDUrlConstructionError)?;
266
267    MethodBuilder::default()
268      .id(id)
269      .type_(MethodType::custom("CompositeJsonWebKey"))
270      .controller(did.into())
271      .data(MethodData::CompositeJwk(key))
272      .build()
273  }
274}
275
276impl Display for VerificationMethod {
277  fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
278    self.fmt_json(f)
279  }
280}
281
282impl AsRef<DIDUrl> for VerificationMethod {
283  fn as_ref(&self) -> &DIDUrl {
284    self.id()
285  }
286}
287
288impl KeyComparable for VerificationMethod {
289  type Key = DIDUrl;
290
291  #[inline]
292  fn key(&self) -> &Self::Key {
293    self.id()
294  }
295}
296
297impl TryFrom<DIDJwk> for VerificationMethod {
298  type Error = Error;
299  fn try_from(did: DIDJwk) -> Result<Self, Self::Error> {
300    let jwk = did.jwk();
301    Self::new_from_jwk(did, jwk, Some("0"))
302  }
303}
304
305impl TryFrom<DIDCompositeJwk> for VerificationMethod {
306  type Error = Error;
307  fn try_from(did: DIDCompositeJwk) -> Result<Self, Self::Error> {
308    let jwk = did.composite_jwk();
309    Self::new_from_compositejwk(did, jwk, Some("0"))
310  }
311}
312
313// Horrible workaround for a tracked serde issue https://github.com/serde-rs/serde/issues/2200. Serde doesn't "consume"
314// the input when deserializing flattened enums (MethodData in this case) causing duplication of data (in this case
315// it ends up in the properties object). This workaround simply removes the duplication.
316#[derive(Deserialize)]
317struct _VerificationMethod {
318  #[serde(deserialize_with = "deserialize_id_with_fragment")]
319  pub(crate) id: DIDUrl,
320  pub(crate) controller: CoreDID,
321  #[serde(rename = "type")]
322  pub(crate) type_: MethodType,
323  #[serde(flatten)]
324  pub(crate) data: MethodData,
325  #[serde(flatten)]
326  pub(crate) properties: Object,
327}
328
329impl From<_VerificationMethod> for VerificationMethod {
330  fn from(value: _VerificationMethod) -> Self {
331    let _VerificationMethod {
332      id,
333      controller,
334      type_,
335      data,
336      mut properties,
337    } = value;
338    let key = match &data {
339      MethodData::PublicKeyBase58(_) => "publicKeyBase58",
340      MethodData::PublicKeyJwk(_) => "publicKeyJwk",
341      MethodData::PublicKeyMultibase(_) => "publicKeyMultibase",
342      MethodData::CompositeJwk(_) => "compositeJwk",
343      MethodData::Custom(CustomMethodData { name, .. }) => name.as_str(),
344    };
345    properties.remove(key);
346
347    VerificationMethod {
348      id,
349      controller,
350      type_,
351      data,
352      properties,
353    }
354  }
355}