1use core::convert::TryFrom;
5use core::fmt::Debug;
6use core::fmt::Display;
7use core::fmt::Formatter;
8use core::str::FromStr;
9
10use identity_core::common::KeyComparable;
11use identity_did::BaseDIDUrl;
12use identity_did::CoreDID;
13use identity_did::Error as DIDError;
14use identity_did::DID;
15use product_common::network_name::NetworkName;
16use ref_cast::ref_cast_custom;
17use ref_cast::RefCastCustom;
18use serde::Deserialize;
19use serde::Serialize;
20
21type Result<T> = std::result::Result<T, DIDError>;
23
24#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, RefCastCustom)]
29#[repr(transparent)]
30#[serde(into = "CoreDID", try_from = "CoreDID")]
31pub struct IotaDID(CoreDID);
32
33impl IotaDID {
34 pub const SCHEME: &'static str = CoreDID::SCHEME;
36
37 pub const METHOD: &'static str = "iota";
39
40 pub const DEFAULT_NETWORK: &'static str = "iota";
44
45 pub const PLACEHOLDER_TAG: &'static str = "0x0000000000000000000000000000000000000000000000000000000000000000";
47
48 pub(crate) const TAG_BYTES_LEN: usize = 32;
50
51 #[ref_cast_custom]
61 pub(crate) const fn from_inner_ref_unchecked(did: &CoreDID) -> &Self;
62
63 pub fn new(bytes: &[u8; Self::TAG_BYTES_LEN], network_name: &NetworkName) -> Self {
82 let tag: String = prefix_hex::encode(bytes);
83 let did: String = format!("did:{}:{}:{}", Self::METHOD, network_name, tag);
84
85 Self::parse(did).expect("DIDs constructed with new should be valid")
86 }
87
88 pub fn from_object_id(object_id: &str, network_name: &NetworkName) -> Self {
90 let did: String = format!("did:{}:{}:{}", Self::METHOD, network_name, object_id);
91 Self::parse(did).expect("DIDs constructed with new should be valid")
92 }
93
94 pub fn placeholder(network_name: &NetworkName) -> Self {
107 Self::new(&[0; 32], network_name)
108 }
109
110 pub fn is_placeholder(&self) -> bool {
122 self.tag_str() == Self::PLACEHOLDER_TAG
123 }
124
125 pub fn parse(input: impl AsRef<str>) -> Result<Self> {
131 CoreDID::parse(input.as_ref().to_lowercase()).and_then(Self::try_from_core)
132 }
133
134 pub fn try_from_core(did: CoreDID) -> Result<Self> {
140 Self::check_validity(&did)?;
141
142 Ok(Self(Self::normalize(did)))
143 }
144
145 pub fn network_str(&self) -> &str {
151 Self::denormalized_components(self.method_id()).0
152 }
153
154 pub fn tag_str(&self) -> &str {
156 Self::denormalized_components(self.method_id()).1
157 }
158
159 pub fn check_validity<D: DID>(did: &D) -> Result<()> {
169 Self::check_method(did)
170 .and_then(|_| Self::check_tag(did))
171 .and_then(|_| Self::check_network(did))
172 }
173
174 pub fn is_valid(did: &CoreDID) -> bool {
179 Self::check_validity(did).is_ok()
180 }
181
182 fn check_method<D: DID>(did: &D) -> Result<()> {
192 (did.method() == Self::METHOD)
193 .then_some(())
194 .ok_or(DIDError::InvalidMethodName)
195 }
196
197 fn check_tag<D: DID>(did: &D) -> Result<()> {
203 let (_, tag) = Self::denormalized_components(did.method_id());
204
205 prefix_hex::decode::<[u8; Self::TAG_BYTES_LEN]>(tag)
207 .map_err(|_| DIDError::InvalidMethodId)
208 .map(|_| ())
209 }
210
211 fn check_network<D: DID>(did: &D) -> Result<()> {
217 let (network_name, _) = Self::denormalized_components(did.method_id());
218 NetworkName::validate_network_name(network_name).map_err(|_| DIDError::Other("invalid network name"))
219 }
220
221 #[allow(clippy::unnecessary_to_owned)]
228 fn normalize(mut did: CoreDID) -> CoreDID {
229 let method_id = did.method_id();
230 let (network, tag) = Self::denormalized_components(method_id);
231 if tag.len() == method_id.len() || network != Self::DEFAULT_NETWORK {
232 did
233 } else {
234 did
235 .set_method_id(tag.to_owned())
236 .expect("normalizing a valid CoreDID should be Ok");
237 did
238 }
239 }
240
241 #[inline(always)]
245 fn denormalized_components(input: &str) -> (&str, &str) {
246 input
247 .find(':')
248 .map(|idx| input.split_at(idx))
249 .map(|(network, tail)| (network, &tail[1..]))
250 .unwrap_or((Self::DEFAULT_NETWORK, input))
252 }
253}
254
255impl FromStr for IotaDID {
256 type Err = DIDError;
257
258 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
259 Self::parse(s)
260 }
261}
262
263impl TryFrom<&str> for IotaDID {
264 type Error = DIDError;
265
266 fn try_from(other: &str) -> std::result::Result<Self, Self::Error> {
267 Self::parse(other)
268 }
269}
270
271impl TryFrom<String> for IotaDID {
272 type Error = DIDError;
273
274 fn try_from(other: String) -> std::result::Result<Self, Self::Error> {
275 Self::parse(other)
276 }
277}
278
279impl Display for IotaDID {
280 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
281 write!(f, "{}", self.0)
282 }
283}
284
285impl From<IotaDID> for CoreDID {
286 fn from(id: IotaDID) -> Self {
287 id.0
288 }
289}
290
291impl From<IotaDID> for String {
292 fn from(did: IotaDID) -> Self {
293 did.into_string()
294 }
295}
296
297impl TryFrom<CoreDID> for IotaDID {
298 type Error = DIDError;
299
300 fn try_from(value: CoreDID) -> std::result::Result<Self, Self::Error> {
301 Self::try_from_core(value)
302 }
303}
304
305impl TryFrom<BaseDIDUrl> for IotaDID {
306 type Error = DIDError;
307
308 fn try_from(other: BaseDIDUrl) -> Result<Self> {
309 let core_did: CoreDID = CoreDID::try_from(other)?;
310 Self::try_from(core_did)
311 }
312}
313
314impl AsRef<CoreDID> for IotaDID {
315 fn as_ref(&self) -> &CoreDID {
316 &self.0
317 }
318}
319
320impl KeyComparable for IotaDID {
321 type Key = CoreDID;
322
323 #[inline]
324 fn key(&self) -> &Self::Key {
325 self.as_ref()
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use identity_did::DIDUrl;
332 use once_cell::sync::Lazy;
333 use proptest::strategy::Strategy;
334 use proptest::*;
335
336 use super::*;
337
338 const VALID_OBJECT_ID_STR: &str = "0xf29dd16310c2100fd1bf568b345fb1cc14d71caa3bd9b5ad735d2bd6d455ca3b";
343
344 const LEN_VALID_OBJECT_ID_STR: usize = VALID_OBJECT_ID_STR.len();
345
346 static VALID_IOTA_DID_STRING: Lazy<String> = Lazy::new(|| format!("did:{}:{}", IotaDID::METHOD, VALID_OBJECT_ID_STR));
347
348 const VALID_NETWORK_NAMES: [&str; 13] = [
351 IotaDID::DEFAULT_NETWORK,
352 "main",
353 "dev",
354 "smr",
355 "rms",
356 "test",
357 "foo",
358 "foobar",
359 "123456",
360 "0",
361 "foo42",
362 "bar123",
363 "42foo",
364 ];
365
366 static VALID_IOTA_DID_STRINGS: Lazy<Vec<String>> = Lazy::new(|| {
367 let network_tag_to_did = |network, tag| format!("did:{}:{}:{}", IotaDID::METHOD, network, tag);
368
369 let valid_strings: Vec<String> = VALID_NETWORK_NAMES
370 .iter()
371 .flat_map(|network| {
372 [VALID_OBJECT_ID_STR, IotaDID::PLACEHOLDER_TAG]
373 .iter()
374 .map(move |tag| network_tag_to_did(network, tag))
375 })
376 .collect();
377
378 assert_eq!(valid_strings.len(), 2 * VALID_NETWORK_NAMES.len());
381
382 valid_strings
383 });
384
385 #[test]
390 fn invalid_check_method() {
391 let did_key_core: CoreDID = CoreDID::parse("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK").unwrap();
392 assert!(matches!(
393 IotaDID::check_method(&did_key_core),
394 Err(DIDError::InvalidMethodName)
395 ));
396 }
397
398 #[test]
399 fn valid_check_method() {
400 let did_iota_core: CoreDID = CoreDID::parse(&*VALID_IOTA_DID_STRING).unwrap();
401 assert!(IotaDID::check_method(&did_iota_core).is_ok());
402 }
403
404 #[test]
405 fn valid_check_network() {
406 let assert_check_network = |input: &str| {
407 let did_core: CoreDID =
408 CoreDID::parse(input).unwrap_or_else(|_| panic!("expected {input} to parse to a valid CoreDID"));
409 assert!(
410 IotaDID::check_network(&did_core).is_ok(),
411 "test: valid_check_network failed with input {input}",
412 );
413 };
414
415 for network_name in VALID_NETWORK_NAMES {
416 let did_string = format!("did:method:{network_name}:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK");
417 assert_check_network(&did_string);
418 }
419
420 assert_check_network("did:method:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK");
421 }
422
423 #[test]
424 fn invalid_check_network() {
425 let mut check_network_executed: bool = false;
433
434 const INVALID_NETWORK_NAMES: [&str; 10] = [
435 "Main",
436 "fOo",
437 "deV",
438 "féta",
439 "",
440 " ",
441 "foo ",
442 " foo",
443 "123456789",
444 "foobar123",
445 ];
446 for network_name in INVALID_NETWORK_NAMES {
447 let did_string: String = format!("did:method:{network_name}:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK");
448 let did_core: CoreDID = {
449 match CoreDID::parse(did_string) {
450 Ok(did_core) => did_core,
451 Err(_) => continue,
452 }
453 };
454
455 assert!(matches!(IotaDID::check_network(&did_core), Err(DIDError::Other(_))));
456 check_network_executed = true;
457 }
458 assert!(
459 check_network_executed,
460 "IotaDID::check_network` should have been executed"
461 );
462 }
463
464 #[test]
465 fn valid_check_tag() {
466 for input in VALID_IOTA_DID_STRINGS.iter() {
467 let did_core: CoreDID = CoreDID::parse(input).unwrap();
468 assert!(
469 IotaDID::check_tag(&did_core).is_ok(),
470 "test: valid_check_method_id failed on input {input}"
471 );
472 }
473
474 let did_other_string: String = format!("did:method:{VALID_OBJECT_ID_STR}");
477 let did_other_with_network: String = format!("did:method:test:{VALID_OBJECT_ID_STR}");
478 let did_other_core: CoreDID = CoreDID::parse(did_other_string).unwrap();
479 let did_other_with_network_core: CoreDID = CoreDID::parse(did_other_with_network).unwrap();
480
481 assert!(IotaDID::check_tag(&did_other_core).is_ok());
482 assert!(IotaDID::check_tag(&did_other_with_network_core).is_ok());
483 }
484
485 #[test]
486 fn invalid_check_tag() {
487 let invalid_method_id_strings = [
488 format!("did:method:main:test:{VALID_OBJECT_ID_STR}"),
490 format!("did:method:{}", &VALID_OBJECT_ID_STR.strip_prefix("0x").unwrap()),
492 format!(
494 "did:method:{}",
495 &VALID_OBJECT_ID_STR.chars().chain("a".chars()).collect::<String>()
496 ),
497 format!("did:method:main:{}", &VALID_OBJECT_ID_STR[..65]),
499 ];
500
501 for input in invalid_method_id_strings {
502 let did_core: CoreDID = CoreDID::parse(input).unwrap();
503 assert!(
504 matches!(IotaDID::check_tag(&did_core), Err(DIDError::InvalidMethodId)),
505 "{}",
506 did_core
507 );
508 }
509 }
510
511 #[test]
516 fn placeholder_produces_a_did_with_expected_string_representation() {
517 assert_eq!(
518 IotaDID::placeholder(&NetworkName::try_from(IotaDID::DEFAULT_NETWORK).unwrap()).as_str(),
519 format!("did:{}:{}", IotaDID::METHOD, IotaDID::PLACEHOLDER_TAG)
520 );
521
522 for name in VALID_NETWORK_NAMES
523 .iter()
524 .filter(|name| *name != &IotaDID::DEFAULT_NETWORK)
525 {
526 let network_name: NetworkName = NetworkName::try_from(*name).unwrap();
527 let did: IotaDID = IotaDID::placeholder(&network_name);
528 assert_eq!(
529 did.as_str(),
530 format!("did:{}:{}:{}", IotaDID::METHOD, name, IotaDID::PLACEHOLDER_TAG)
531 );
532 }
533 }
534
535 #[test]
536 fn normalization_in_constructors() {
537 let did_with_default_network_string: String = format!(
538 "did:{}:{}:{}",
539 IotaDID::METHOD,
540 IotaDID::DEFAULT_NETWORK,
541 VALID_OBJECT_ID_STR
542 );
543 let expected_normalization_string_representation: String =
544 format!("did:{}:{}", IotaDID::METHOD, VALID_OBJECT_ID_STR);
545
546 assert_eq!(
547 IotaDID::parse(did_with_default_network_string).unwrap().as_str(),
548 expected_normalization_string_representation
549 );
550 }
551
552 #[test]
553 fn parse_valid() {
554 for did_str in VALID_IOTA_DID_STRINGS.iter() {
555 assert!(IotaDID::parse(did_str).is_ok());
556 }
557 }
558
559 #[test]
560 fn parse_invalid() {
561 let execute_assertions = |valid_object_id: &str| {
562 assert!(matches!(
563 IotaDID::parse(format!("dod:{}:{}", IotaDID::METHOD, valid_object_id)),
564 Err(DIDError::InvalidScheme)
565 ));
566
567 assert!(matches!(
568 IotaDID::parse(format!("did:key:{valid_object_id}")),
569 Err(DIDError::InvalidMethodName)
570 ));
571
572 assert!(matches!(
574 IotaDID::parse(format!("did:{}:123456789:{}", IotaDID::METHOD, valid_object_id)),
575 Err(DIDError::Other(_))
576 ));
577
578 assert!(matches!(
580 IotaDID::parse(format!("did:{}:féta:{}", IotaDID::METHOD, valid_object_id)),
581 Err(DIDError::InvalidMethodId)
582 ));
583
584 assert!(matches!(
586 IotaDID::parse(format!("did:{}:", IotaDID::METHOD)),
587 Err(DIDError::InvalidMethodId)
588 ));
589
590 assert!(matches!(
592 IotaDID::parse(format!("did:{}:test:foo:{}", IotaDID::METHOD, valid_object_id)),
593 Err(DIDError::InvalidMethodId)
594 ));
595 };
596
597 execute_assertions(IotaDID::PLACEHOLDER_TAG);
598 execute_assertions(VALID_OBJECT_ID_STR);
599 }
600
601 #[cfg(feature = "iota-client")]
606 proptest! {
607 #[test]
608 fn property_based_new(bytes in proptest::prelude::any::<[u8;32]>()) {
609 for network_name in VALID_NETWORK_NAMES.iter().map(|name| NetworkName::try_from(*name).unwrap()) {
610 IotaDID::new(&bytes, &network_name);
612 }
613 }
614 }
615
616 fn arbitrary_object_id_string_replica() -> impl Strategy<Value = String> {
617 proptest::string::string_regex(&format!("0x([a-f]|[0-9]){{{}}}", (LEN_VALID_OBJECT_ID_STR - 2)))
618 .expect("regex should be ok")
619 }
620
621 proptest! {
622 #[test]
623 fn valid_object_id_string_replicas(tag in arbitrary_object_id_string_replica()) {
624 let did : String = format!("did:{}:{}", IotaDID::METHOD, tag);
625 assert!(
626 IotaDID::parse(did).is_ok()
627 );
628 }
629 }
630
631 fn arbitrary_invalid_tag() -> impl Strategy<Value = String> {
632 proptest::string::string_regex("[[:^alpha:]|[a-z]|[1-9]]*")
633 .expect("regex should be ok")
634 .prop_map(|arb_string| {
635 if arb_string
636 .chars()
637 .all(|c| c.is_ascii_hexdigit() && c.is_ascii_lowercase())
638 && arb_string.len() == LEN_VALID_OBJECT_ID_STR
639 && arb_string.starts_with("0x")
640 {
641 let mut counter = 0;
644 arb_string
645 .chars()
646 .rev()
647 .map(|value| {
648 if value == '0' && counter == 0 {
649 counter += 1;
650 'é'
651 } else {
652 value
653 }
654 })
655 .collect::<String>()
656 } else {
657 arb_string
658 }
659 })
660 }
661
662 proptest! {
663 #[test]
664 fn invalid_tag_property_based_parse(tag in arbitrary_invalid_tag()) {
665 let did: String = format!("did:{}:{}", IotaDID::METHOD, tag);
666 assert!(
667 IotaDID::parse(did).is_err()
668 );
669 }
670 }
671
672 fn arbitrary_delimiter_mixed_in_prefix_hex() -> impl Strategy<Value = String> {
673 proptest::string::string_regex("0x([a-f]|[:]|[0-9])*").expect("regex should be ok")
674 }
675
676 proptest! {
677 #[test]
678 fn invalid_hex_mixed_with_delimiter(tag in arbitrary_delimiter_mixed_in_prefix_hex()) {
679 let did: String = format!("did:{}:{}", IotaDID::METHOD, tag);
680 assert!(IotaDID::parse(did).is_err());
681 }
682 }
683
684 #[test]
688 fn test_network() {
689 let execute_assertions = |valid_object_id: &str| {
690 let did: IotaDID = format!("did:{}:{}", IotaDID::METHOD, valid_object_id).parse().unwrap();
691 assert_eq!(did.network_str(), IotaDID::DEFAULT_NETWORK);
692
693 let did: IotaDID = format!("did:{}:dev:{}", IotaDID::METHOD, valid_object_id)
694 .parse()
695 .unwrap();
696 assert_eq!(did.network_str(), "dev");
697
698 let did: IotaDID = format!("did:{}:test:{}", IotaDID::METHOD, valid_object_id)
699 .parse()
700 .unwrap();
701 assert_eq!(did.network_str(), "test");
702
703 let did: IotaDID = format!("did:{}:custom:{}", IotaDID::METHOD, valid_object_id)
704 .parse()
705 .unwrap();
706 assert_eq!(did.network_str(), "custom");
707 };
708
709 execute_assertions(IotaDID::PLACEHOLDER_TAG);
710 execute_assertions(VALID_OBJECT_ID_STR);
711 }
712
713 #[test]
714 fn test_tag() {
715 let execute_assertions = |valid_object_id: &str| {
716 let did: IotaDID = format!("did:{}:{}", IotaDID::METHOD, valid_object_id).parse().unwrap();
717 assert_eq!(did.tag_str(), valid_object_id);
718
719 let did: IotaDID = format!(
720 "did:{}:{}:{}",
721 IotaDID::METHOD,
722 IotaDID::DEFAULT_NETWORK,
723 valid_object_id
724 )
725 .parse()
726 .unwrap();
727 assert_eq!(did.tag_str(), valid_object_id);
728
729 let did: IotaDID = format!("did:{}:dev:{}", IotaDID::METHOD, valid_object_id)
730 .parse()
731 .unwrap();
732 assert_eq!(did.tag_str(), valid_object_id);
733
734 let did: IotaDID = format!("did:{}:custom:{}", IotaDID::METHOD, valid_object_id)
735 .parse()
736 .unwrap();
737 assert_eq!(did.tag_str(), valid_object_id);
738 };
739 execute_assertions(IotaDID::PLACEHOLDER_TAG);
740 execute_assertions(VALID_OBJECT_ID_STR);
741 }
742
743 #[test]
748 fn test_parse_did_url_valid() {
749 let execute_assertions = |valid_object_id: &str| {
750 assert!(DIDUrl::parse(format!("did:{}:{}", IotaDID::METHOD, valid_object_id)).is_ok());
751 assert!(DIDUrl::parse(format!("did:{}:{}#fragment", IotaDID::METHOD, valid_object_id)).is_ok());
752 assert!(DIDUrl::parse(format!(
753 "did:{}:{}?somequery=somevalue",
754 IotaDID::METHOD,
755 valid_object_id
756 ))
757 .is_ok());
758 assert!(DIDUrl::parse(format!(
759 "did:{}:{}?somequery=somevalue#fragment",
760 IotaDID::METHOD,
761 valid_object_id
762 ))
763 .is_ok());
764
765 assert!(DIDUrl::parse(format!("did:{}:main:{}", IotaDID::METHOD, valid_object_id)).is_ok());
766 assert!(DIDUrl::parse(format!("did:{}:main:{}#fragment", IotaDID::METHOD, valid_object_id)).is_ok());
767 assert!(DIDUrl::parse(format!(
768 "did:{}:main:{}?somequery=somevalue",
769 IotaDID::METHOD,
770 valid_object_id
771 ))
772 .is_ok());
773 assert!(DIDUrl::parse(format!(
774 "did:{}:main:{}?somequery=somevalue#fragment",
775 IotaDID::METHOD,
776 valid_object_id
777 ))
778 .is_ok());
779
780 assert!(DIDUrl::parse(format!("did:{}:dev:{}", IotaDID::METHOD, valid_object_id)).is_ok());
781 assert!(DIDUrl::parse(format!("did:{}:dev:{}#fragment", IotaDID::METHOD, valid_object_id)).is_ok());
782 assert!(DIDUrl::parse(format!(
783 "did:{}:dev:{}?somequery=somevalue",
784 IotaDID::METHOD,
785 valid_object_id
786 ))
787 .is_ok());
788 assert!(DIDUrl::parse(format!(
789 "did:{}:dev:{}?somequery=somevalue#fragment",
790 IotaDID::METHOD,
791 valid_object_id
792 ))
793 .is_ok());
794
795 assert!(DIDUrl::parse(format!("did:{}:custom:{}", IotaDID::METHOD, valid_object_id)).is_ok());
796 assert!(DIDUrl::parse(format!("did:{}:custom:{}#fragment", IotaDID::METHOD, valid_object_id)).is_ok());
797 assert!(DIDUrl::parse(format!(
798 "did:{}:custom:{}?somequery=somevalue",
799 IotaDID::METHOD,
800 valid_object_id
801 ))
802 .is_ok());
803 assert!(DIDUrl::parse(format!(
804 "did:{}:custom:{}?somequery=somevalue#fragment",
805 IotaDID::METHOD,
806 valid_object_id
807 ))
808 .is_ok());
809 };
810 execute_assertions(IotaDID::PLACEHOLDER_TAG);
811 execute_assertions(VALID_OBJECT_ID_STR);
812 }
813
814 #[test]
815 fn valid_url_setters() {
816 let execute_assertions = |valid_object_id: &str| {
817 let mut did_url: DIDUrl = IotaDID::parse(format!("did:{}:{}", IotaDID::METHOD, valid_object_id))
818 .unwrap()
819 .into_url();
820
821 did_url.set_path(Some("/foo")).unwrap();
822 did_url.set_query(Some("diff=true")).unwrap();
823 did_url.set_fragment(Some("foo")).unwrap();
824
825 assert_eq!(did_url.path(), Some("/foo"));
826 assert_eq!(did_url.query(), Some("diff=true"));
827 assert_eq!(did_url.fragment(), Some("foo"));
828 };
829 execute_assertions(IotaDID::PLACEHOLDER_TAG);
830 execute_assertions(VALID_OBJECT_ID_STR);
831 }
832}