identity_iota_core/state_metadata/
document.rs

1// Copyright 2020-2023 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use identity_core::convert::FromJson;
5use identity_core::convert::ToJson;
6use identity_did::CoreDID;
7use identity_document::document::CoreDocument;
8use once_cell::sync::Lazy;
9use serde::Deserialize;
10use serde::Serialize;
11
12use crate::error::Result;
13use crate::Error;
14use crate::IotaDID;
15use crate::IotaDocument;
16use crate::IotaDocumentMetadata;
17
18use super::StateMetadataEncoding;
19use super::StateMetadataVersion;
20
21pub(crate) static PLACEHOLDER_DID: Lazy<CoreDID> = Lazy::new(|| CoreDID::parse("did:0:0").unwrap());
22
23/// Magic bytes used to mark DID documents.
24const DID_MARKER: &[u8] = b"DID";
25
26/// Intermediate representation of the DID document as it is contained in the identity.
27///
28/// DID instances in the document are replaced by the `PLACEHOLDER_DID`.
29#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
30pub struct StateMetadataDocument {
31  #[serde(rename = "doc")]
32  pub(crate) document: CoreDocument,
33  #[serde(rename = "meta")]
34  pub(crate) metadata: IotaDocumentMetadata,
35}
36
37impl StateMetadataDocument {
38  /// Transforms the document into a [`IotaDocument`] by replacing all placeholders with `original_did`.
39  pub fn into_iota_document(self, original_did: &IotaDID) -> Result<IotaDocument> {
40    let Self { document, metadata } = self;
41
42    // Transform identifiers: Replace placeholder identifiers, and ensure that `id` and `controller` adhere to the
43    // specification.
44    let replace_placeholder_with_method_check = |did: CoreDID| -> Result<CoreDID> {
45      if did == PLACEHOLDER_DID.as_ref() {
46        Ok(CoreDID::from(original_did.clone()))
47      } else {
48        IotaDID::check_validity(&did).map_err(Error::DIDSyntaxError)?;
49        Ok(did)
50      }
51    };
52    let [id_update, controller_update] = [replace_placeholder_with_method_check; 2];
53
54    // Methods and services are not required to be IOTA UTXO DIDs, but we still want to replace placeholders
55    let replace_placeholder = |did: CoreDID| -> Result<CoreDID> {
56      if did == PLACEHOLDER_DID.as_ref() {
57        Ok(CoreDID::from(original_did.clone()))
58      } else {
59        Ok(did)
60      }
61    };
62    let [methods_update, service_update] = [replace_placeholder; 2];
63
64    let document = document.try_map(
65      id_update,
66      controller_update,
67      methods_update,
68      service_update,
69      crate::error::Error::InvalidDoc,
70    )?;
71
72    Ok(IotaDocument { document, metadata })
73  }
74
75  /// Returns the corresponding [`IotaDocument`] with DID replaced by DID placeholder `did:0:0`.
76  pub fn into_iota_document_with_placeholders(self) -> IotaDocument {
77    IotaDocument {
78      document: self.document,
79      metadata: self.metadata,
80    }
81  }
82
83  /// Pack a [`StateMetadataDocument`] into bytes, suitable for storing in an identity,
84  /// according to the given `encoding`.
85  pub fn pack(self, encoding: StateMetadataEncoding) -> Result<Vec<u8>> {
86    let encoded_message_data: Vec<u8> = match encoding {
87      StateMetadataEncoding::Json => self
88        .to_json_vec()
89        .map_err(|err| Error::SerializationError("failed to serialize document to JSON", Some(err)))?,
90    };
91
92    // Prepend flags and length.
93    let encoded_message_data_with_flags =
94      add_flags_to_message(encoded_message_data, StateMetadataVersion::CURRENT, encoding)?;
95    Ok(encoded_message_data_with_flags)
96  }
97
98  /// Unpack bytes into a [`StateMetadataDocument`].
99  pub fn unpack(data: &[u8]) -> Result<Self> {
100    // Check marker.
101    let marker: &[u8] = data
102      .get(0..=2)
103      .ok_or(identity_document::Error::InvalidDocument(
104        "state metadata decoding: expected DID marker at offset [0..=2]",
105        None,
106      ))
107      .map_err(Error::InvalidDoc)?;
108    if marker != DID_MARKER {
109      return Err(Error::InvalidStateMetadata("missing `DID` marker"));
110    }
111
112    // Check version.
113    let version: StateMetadataVersion = StateMetadataVersion::try_from(
114      *data
115        .get(3)
116        .ok_or(identity_document::Error::InvalidDocument(
117          "state metadata decoding: expected version at offset 3",
118          None,
119        ))
120        .map_err(Error::InvalidDoc)?,
121    )?;
122    if version != StateMetadataVersion::V1 {
123      return Err(Error::InvalidStateMetadata("unsupported version"));
124    }
125
126    // Decode data.
127    let encoding: StateMetadataEncoding = StateMetadataEncoding::try_from(
128      *data
129        .get(4)
130        .ok_or(identity_document::Error::InvalidDocument(
131          "state metadata decoding: expected encoding at offset 4",
132          None,
133        ))
134        .map_err(Error::InvalidDoc)?,
135    )?;
136
137    let data_len_packed: [u8; 2] = data
138      .get(5..=6)
139      .ok_or(identity_document::Error::InvalidDocument(
140        "state metadata decoding: expected data length at offset [5..=6]",
141        None,
142      ))
143      .map_err(Error::InvalidDoc)?
144      .try_into()
145      .map_err(|_| {
146        identity_document::Error::InvalidDocument("state metadata decoding: data length conversion error", None)
147      })
148      .map_err(Error::InvalidDoc)?;
149    let data_len: u16 = u16::from_le_bytes(data_len_packed);
150
151    let data: &[u8] = data
152      .get(7..(7 + data_len as usize))
153      .ok_or(identity_document::Error::InvalidDocument(
154        "state metadata decoding: encoded document shorter than length prefix",
155        None,
156      ))
157      .map_err(Error::InvalidDoc)?;
158
159    match encoding {
160      StateMetadataEncoding::Json => StateMetadataDocument::from_json_slice(data).map_err(|err| {
161        Error::SerializationError(
162          "state metadata decoding: failed to deserialize JSON document",
163          Some(err),
164        )
165      }),
166    }
167  }
168}
169
170/// Prepends the message flags and marker magic bytes to the data in the following order:
171/// `[marker, version, encoding, data length, data]`.
172fn add_flags_to_message(
173  mut data: Vec<u8>,
174  version: StateMetadataVersion,
175  encoding: StateMetadataEncoding,
176) -> Result<Vec<u8>> {
177  let data_len: u16 =
178    u16::try_from(data.len()).map_err(|_| Error::SerializationError("failed to convert usize to u16", None))?;
179  let data_len_packed: [u8; 2] = data_len.to_le_bytes();
180  let mut buffer: Vec<u8> = Vec::with_capacity(DID_MARKER.len() + 1 + 1 + data_len_packed.len() + data_len as usize);
181  buffer.extend_from_slice(DID_MARKER);
182  buffer.push(version as u8);
183  buffer.push(encoding as u8);
184  buffer.extend_from_slice(&data_len_packed);
185  buffer.append(&mut data);
186  Ok(buffer)
187}
188
189impl From<IotaDocument> for StateMetadataDocument {
190  /// Transforms a [`IotaDocument`] into its state metadata representation by replacing all
191  /// occurrences of its did with a placeholder.
192  fn from(document: IotaDocument) -> Self {
193    let id: IotaDID = document.id().clone();
194    let IotaDocument { document, metadata } = document;
195
196    // Replace self-referential identifiers with a placeholder, but not others.
197    let replace_id_with_placeholder = |did: CoreDID| -> CoreDID {
198      if &did == id.as_ref() {
199        PLACEHOLDER_DID.clone()
200      } else {
201        did
202      }
203    };
204
205    let [id_update, controller_update, methods_update, service_update] = [replace_id_with_placeholder; 4];
206
207    StateMetadataDocument {
208      document: document.map_unchecked(id_update, controller_update, methods_update, service_update),
209      metadata,
210    }
211  }
212}
213
214#[cfg(test)]
215mod tests {
216  use identity_core::common::Object;
217  use identity_core::common::OneOrSet;
218  use identity_core::common::Url;
219  use identity_did::CoreDID;
220  use identity_did::DID;
221  use identity_verification::MethodScope;
222
223  use crate::state_metadata::document::DID_MARKER;
224  use crate::state_metadata::PLACEHOLDER_DID;
225  use crate::test_utils::generate_method;
226  use crate::IotaDID;
227  use crate::IotaDocument;
228  use crate::StateMetadataDocument;
229  use crate::StateMetadataEncoding;
230  use crate::StateMetadataVersion;
231  use identity_document::service::Service;
232
233  struct TestSetup {
234    document: IotaDocument,
235    did_self: IotaDID,
236    did_foreign: IotaDID,
237  }
238
239  fn test_document() -> TestSetup {
240    let did_self =
241      IotaDID::parse("did:iota:0x8036235b6b5939435a45d68bcea7890eef399209a669c8c263fac7f5089b2ec6").unwrap();
242    let did_foreign =
243      IotaDID::parse("did:iota:0x71b709dff439f1ac9dd2b9c2e28db0807156b378e13bfa3605ce665aa0d0fdca").unwrap();
244
245    let mut document: IotaDocument = IotaDocument::new_with_id(did_self.clone());
246    document
247      .insert_method(generate_method(&did_self, "did-self"), MethodScope::VerificationMethod)
248      .unwrap();
249
250    document
251      .insert_method(
252        generate_method(&did_foreign, "did-foreign"),
253        MethodScope::authentication(),
254      )
255      .unwrap();
256
257    assert!(document
258      .insert_service(
259        Service::builder(Object::new())
260          .id(document.id().to_url().join("#my-service").unwrap())
261          .type_("RevocationList2022")
262          .service_endpoint(Url::parse("https://example.com/xyzabc").unwrap())
263          .build()
264          .unwrap()
265      )
266      .is_ok());
267
268    assert!(document
269      .insert_service(
270        Service::builder(Object::new())
271          .id(did_foreign.to_url().join("#my-foreign-service").unwrap())
272          .type_("RevocationList2022")
273          .service_endpoint(Url::parse("https://example.com/0xf4c42e9da").unwrap())
274          .build()
275          .unwrap()
276      )
277      .is_ok());
278
279    document
280      .also_known_as_mut()
281      .append(Url::parse("did:example:abc").unwrap());
282    document
283      .also_known_as_mut()
284      .append(Url::parse("did:example:xyz").unwrap());
285
286    let controllers = OneOrSet::try_from(vec![did_foreign.clone().into(), did_self.clone().into()]).unwrap();
287    *document.core_document_mut().controller_mut() = Some(controllers);
288
289    TestSetup {
290      document,
291      did_self,
292      did_foreign,
293    }
294  }
295
296  #[test]
297  fn test_transformation_roundtrip() {
298    let TestSetup {
299      document,
300      did_self,
301      did_foreign,
302    } = test_document();
303
304    let state_metadata_doc: StateMetadataDocument = StateMetadataDocument::from(document.clone());
305
306    assert_eq!(
307      state_metadata_doc
308        .document
309        .resolve_method("#did-self", None)
310        .unwrap()
311        .id()
312        .did(),
313      <CoreDID as AsRef<CoreDID>>::as_ref(PLACEHOLDER_DID.as_ref())
314    );
315
316    assert_eq!(
317      state_metadata_doc
318        .document
319        .resolve_method("#did-foreign", None)
320        .unwrap()
321        .id()
322        .did(),
323      did_foreign.as_ref()
324    );
325
326    assert_eq!(
327      state_metadata_doc
328        .document
329        .service()
330        .iter()
331        .find(|service| service.id().fragment().unwrap() == "my-foreign-service")
332        .unwrap()
333        .id()
334        .did(),
335      did_foreign.as_ref()
336    );
337
338    assert_eq!(
339      state_metadata_doc
340        .document
341        .service()
342        .iter()
343        .find(|service| service.id().fragment().unwrap() == "my-service")
344        .unwrap()
345        .id()
346        .did(),
347      <CoreDID as AsRef<CoreDID>>::as_ref(PLACEHOLDER_DID.as_ref())
348    );
349
350    let controllers = state_metadata_doc.document.controller().unwrap();
351    assert_eq!(controllers.get(0).unwrap(), did_foreign.as_ref());
352    assert_eq!(
353      controllers.get(1).unwrap(),
354      <CoreDID as AsRef<CoreDID>>::as_ref(PLACEHOLDER_DID.as_ref())
355    );
356
357    let iota_document = state_metadata_doc.into_iota_document(&did_self).unwrap();
358    assert_eq!(iota_document, document);
359  }
360
361  #[test]
362  fn test_packing_roundtrip() {
363    let TestSetup { document, .. } = test_document();
364
365    let state_metadata_doc: StateMetadataDocument = StateMetadataDocument::from(document);
366    let packed_bytes: Vec<u8> = state_metadata_doc.clone().pack(StateMetadataEncoding::Json).unwrap();
367
368    let unpacked_doc = StateMetadataDocument::unpack(&packed_bytes).unwrap();
369    assert_eq!(state_metadata_doc.metadata.created, unpacked_doc.metadata.created);
370    assert_eq!(state_metadata_doc.metadata.updated, unpacked_doc.metadata.updated);
371    assert_eq!(
372      state_metadata_doc.metadata.deactivated,
373      unpacked_doc.metadata.deactivated
374    );
375
376    assert_eq!(state_metadata_doc.document.id(), unpacked_doc.document.id());
377    assert_eq!(
378      state_metadata_doc.document.also_known_as(),
379      unpacked_doc.document.also_known_as()
380    );
381    assert_eq!(
382      state_metadata_doc.document.verification_method(),
383      unpacked_doc.document.verification_method()
384    );
385    assert_eq!(
386      state_metadata_doc.document.authentication(),
387      unpacked_doc.document.authentication()
388    );
389    assert_eq!(
390      state_metadata_doc.document.assertion_method(),
391      unpacked_doc.document.assertion_method()
392    );
393    assert_eq!(
394      state_metadata_doc.document.key_agreement(),
395      unpacked_doc.document.key_agreement()
396    );
397    assert_eq!(
398      state_metadata_doc.document.capability_delegation(),
399      unpacked_doc.document.capability_delegation()
400    );
401    assert_eq!(state_metadata_doc.document.service(), unpacked_doc.document.service());
402    assert_eq!(
403      state_metadata_doc.document.properties(),
404      unpacked_doc.document.properties()
405    );
406  }
407
408  #[test]
409  fn test_pack_format() {
410    // Changing the serialization is a breaking change!
411    let TestSetup { document, .. } = test_document();
412    let state_metadata_doc: StateMetadataDocument = StateMetadataDocument::from(document);
413    let packed: Vec<u8> = state_metadata_doc.clone().pack(StateMetadataEncoding::Json).unwrap();
414    let expected_payload: String = format!(
415      "{{\"doc\":{},\"meta\":{}}}",
416      state_metadata_doc.document, state_metadata_doc.metadata
417    );
418
419    // DID marker.
420    assert_eq!(&packed[0..3], DID_MARKER);
421    // Version.
422    assert_eq!(packed[3], StateMetadataVersion::V1 as u8);
423    // Encoding.
424    assert_eq!(packed[4], StateMetadataEncoding::Json as u8);
425    // JSON length.
426    assert_eq!(&packed[5..=6], (expected_payload.len() as u16).to_le_bytes().as_ref());
427    // JSON payload.
428    assert_eq!(&packed[7..], expected_payload.as_bytes());
429  }
430
431  #[test]
432  fn test_no_controller() {
433    let TestSetup {
434      mut document, did_self, ..
435    } = test_document();
436    *document.core_document_mut().controller_mut() = None;
437    let state_metadata_doc: StateMetadataDocument = StateMetadataDocument::from(document);
438    let packed: Vec<u8> = state_metadata_doc.clone().pack(StateMetadataEncoding::Json).unwrap();
439    let expected_payload: String = format!(
440      "{{\"doc\":{},\"meta\":{}}}",
441      state_metadata_doc.document, state_metadata_doc.metadata
442    );
443    assert_eq!(&packed[7..], expected_payload.as_bytes());
444    let unpacked = StateMetadataDocument::unpack(&packed).unwrap();
445    assert_eq!(
446      unpacked
447        .into_iota_document(&did_self)
448        .unwrap()
449        .controller()
450        .collect::<Vec<_>>()
451        .len(),
452      0
453    );
454  }
455
456  #[test]
457  fn test_unpack_length_prefix() {
458    // Changing the serialization is a breaking change!
459    let TestSetup { document, .. } = test_document();
460    let state_metadata_doc: StateMetadataDocument = StateMetadataDocument::from(document);
461    let mut packed: Vec<u8> = state_metadata_doc.clone().pack(StateMetadataEncoding::Json).unwrap();
462    let original_length = u16::from_le_bytes(packed[5..=6].try_into().unwrap());
463
464    // INVALID: length is too long.
465    let longer = (original_length + 1_u16).to_le_bytes();
466    packed[5] = longer[0];
467    packed[6] = longer[1];
468    assert!(StateMetadataDocument::unpack(&packed).is_err());
469
470    // INVALID: length is too long.
471    let max: [u8; 2] = u16::MAX.to_le_bytes();
472    packed[5] = max[0];
473    packed[6] = max[1];
474    assert!(StateMetadataDocument::unpack(&packed).is_err());
475
476    // INVALID: length is too short (JSON deserialization fails).
477    let shorter = (original_length - 1_u16).to_le_bytes();
478    packed[5] = shorter[0];
479    packed[6] = shorter[1];
480    assert!(StateMetadataDocument::unpack(&packed).is_err());
481
482    // INVALID: length is too short (JSON deserialization fails).
483    let min = 0_u16.to_le_bytes();
484    packed[5] = min[0];
485    packed[6] = min[1];
486    assert!(StateMetadataDocument::unpack(&packed).is_err());
487
488    // VALID: length is just right.
489    let original = original_length.to_le_bytes();
490    packed[5] = original[0];
491    packed[6] = original[1];
492
493    let unpacked_doc = StateMetadataDocument::unpack(&packed).unwrap();
494    // Controller and State Controller are set to None when packing
495    assert_eq!(state_metadata_doc.metadata.created, unpacked_doc.metadata.created);
496    assert_eq!(state_metadata_doc.metadata.updated, unpacked_doc.metadata.updated);
497    assert_eq!(
498      state_metadata_doc.metadata.deactivated,
499      unpacked_doc.metadata.deactivated
500    );
501
502    assert_eq!(state_metadata_doc.document.id(), unpacked_doc.document.id());
503    assert_eq!(
504      state_metadata_doc.document.also_known_as(),
505      unpacked_doc.document.also_known_as()
506    );
507    assert_eq!(
508      state_metadata_doc.document.verification_method(),
509      unpacked_doc.document.verification_method()
510    );
511    assert_eq!(
512      state_metadata_doc.document.authentication(),
513      unpacked_doc.document.authentication()
514    );
515    assert_eq!(
516      state_metadata_doc.document.assertion_method(),
517      unpacked_doc.document.assertion_method()
518    );
519    assert_eq!(
520      state_metadata_doc.document.key_agreement(),
521      unpacked_doc.document.key_agreement()
522    );
523    assert_eq!(
524      state_metadata_doc.document.capability_delegation(),
525      unpacked_doc.document.capability_delegation()
526    );
527    assert_eq!(state_metadata_doc.document.service(), unpacked_doc.document.service());
528    assert_eq!(
529      state_metadata_doc.document.properties(),
530      unpacked_doc.document.properties()
531    );
532  }
533}