1use crate::freshness::Freshness;
4use crate::witness::WitnessQuorum;
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct VerificationReport {
15 pub status: VerificationStatus,
17 pub chain: Vec<ChainLink>,
19 pub warnings: Vec<String>,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub witness_quorum: Option<WitnessQuorum>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub anchored: Option<auths_keri::AnchorStatus>,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub duplicity_warning: Option<crate::duplicity::DuplicityReport>,
40 #[serde(default)]
46 pub freshness: Freshness,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub as_of: Option<u128>,
54}
55
56impl VerificationReport {
57 pub fn is_valid(&self) -> bool {
61 matches!(self.status, VerificationStatus::Valid) && self.duplicity_warning.is_none()
62 }
63
64 pub fn valid(chain: Vec<ChainLink>) -> Self {
70 Self {
71 status: VerificationStatus::Valid,
72 chain,
73 warnings: Vec::new(),
74 witness_quorum: None,
75 anchored: None,
76 duplicity_warning: None,
77 freshness: Freshness::Unknown,
78 as_of: None,
79 }
80 }
81
82 pub fn with_status(status: VerificationStatus, chain: Vec<ChainLink>) -> Self {
87 Self {
88 status,
89 chain,
90 warnings: Vec::new(),
91 witness_quorum: None,
92 anchored: None,
93 duplicity_warning: None,
94 freshness: Freshness::Unknown,
95 as_of: None,
96 }
97 }
98
99 pub fn with_duplicity_warning(mut self, warning: crate::duplicity::DuplicityReport) -> Self {
103 self.duplicity_warning = Some(warning);
104 self
105 }
106
107 pub fn freshness(&self) -> Freshness {
110 self.freshness
111 }
112
113 pub fn with_freshness(mut self, freshness: Freshness) -> Self {
123 self.freshness = freshness;
124 self
125 }
126
127 pub fn as_of(&self) -> Option<u128> {
130 self.as_of
131 }
132
133 pub fn with_as_of(mut self, as_of: u128) -> Self {
140 self.as_of = Some(as_of);
141 self
142 }
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
147#[serde(tag = "type")]
148pub enum VerificationStatus {
149 Valid,
151 Expired {
153 at: DateTime<Utc>,
155 },
156 Revoked {
158 at: Option<DateTime<Utc>>,
160 },
161 InvalidSignature {
163 step: usize,
165 },
166 BrokenChain {
168 missing_link: String,
170 },
171 InsufficientWitnesses {
173 required: usize,
175 verified: usize,
177 },
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
182pub struct ChainLink {
183 pub issuer: String,
185 pub subject: String,
187 pub valid: bool,
189 pub error: Option<String>,
191}
192
193impl ChainLink {
194 pub fn valid(issuer: String, subject: String) -> Self {
196 Self {
197 issuer,
198 subject,
199 valid: true,
200 error: None,
201 }
202 }
203
204 pub fn invalid(issuer: String, subject: String, error: String) -> Self {
206 Self {
207 issuer,
208 subject,
209 valid: false,
210 error: Some(error),
211 }
212 }
213}
214
215use std::borrow::Borrow;
220use std::fmt;
221use std::ops::Deref;
222use std::str::FromStr;
223
224#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
239#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
240#[repr(transparent)]
241pub struct IdentityDID(String);
242
243impl IdentityDID {
244 pub(crate) fn new_unchecked<S: Into<String>>(s: S) -> Self {
248 Self(s.into())
249 }
250
251 pub fn parse(s: &str) -> Result<Self, DidParseError> {
263 match s.strip_prefix("did:keri:") {
264 Some("") => Err(DidParseError::EmptyIdentifier),
265 Some(_) => Ok(Self(s.to_string())),
266 None => Err(DidParseError::InvalidIdentityPrefix(s.to_string())),
267 }
268 }
269
270 pub fn from_prefix(prefix: &str) -> Result<Self, DidParseError> {
282 if prefix.is_empty() {
283 return Err(DidParseError::EmptyIdentifier);
284 }
285 Ok(Self(format!("did:keri:{}", prefix)))
286 }
287
288 pub fn prefix(&self) -> &str {
297 self.0.strip_prefix("did:keri:").unwrap_or(&self.0)
298 }
299
300 pub fn as_str(&self) -> &str {
302 &self.0
303 }
304
305 pub fn into_inner(self) -> String {
307 self.0
308 }
309}
310
311impl fmt::Display for IdentityDID {
312 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313 f.write_str(&self.0)
314 }
315}
316
317impl FromStr for IdentityDID {
318 type Err = DidParseError;
319
320 fn from_str(s: &str) -> Result<Self, Self::Err> {
321 Self::parse(s)
322 }
323}
324
325impl TryFrom<&str> for IdentityDID {
326 type Error = DidParseError;
327
328 fn try_from(s: &str) -> Result<Self, Self::Error> {
329 Self::parse(s)
330 }
331}
332
333impl TryFrom<String> for IdentityDID {
334 type Error = DidParseError;
335
336 fn try_from(s: String) -> Result<Self, Self::Error> {
337 Self::parse(&s)
338 }
339}
340
341impl TryFrom<&auths_keri::Prefix> for IdentityDID {
361 type Error = DidParseError;
362
363 fn try_from(prefix: &auths_keri::Prefix) -> Result<Self, Self::Error> {
364 Self::from_prefix(prefix.as_str())
365 }
366}
367
368impl TryFrom<auths_keri::Prefix> for IdentityDID {
369 type Error = DidParseError;
370
371 fn try_from(prefix: auths_keri::Prefix) -> Result<Self, Self::Error> {
372 Self::from_prefix(prefix.as_str())
373 }
374}
375
376impl From<IdentityDID> for String {
377 fn from(did: IdentityDID) -> String {
378 did.0
379 }
380}
381
382impl<'de> serde::Deserialize<'de> for IdentityDID {
383 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
384 where
385 D: serde::Deserializer<'de>,
386 {
387 let s = String::deserialize(deserializer)?;
388 Self::parse(&s).map_err(serde::de::Error::custom)
389 }
390}
391
392impl Deref for IdentityDID {
393 type Target = str;
394
395 fn deref(&self) -> &Self::Target {
396 &self.0
397 }
398}
399
400impl AsRef<str> for IdentityDID {
401 fn as_ref(&self) -> &str {
402 &self.0
403 }
404}
405
406impl Borrow<str> for IdentityDID {
407 fn borrow(&self) -> &str {
408 &self.0
409 }
410}
411
412impl PartialEq<str> for IdentityDID {
413 fn eq(&self, other: &str) -> bool {
414 self.0 == other
415 }
416}
417
418impl PartialEq<&str> for IdentityDID {
419 fn eq(&self, other: &&str) -> bool {
420 self.0 == *other
421 }
422}
423
424impl PartialEq<IdentityDID> for str {
425 fn eq(&self, other: &IdentityDID) -> bool {
426 self == other.0
427 }
428}
429
430impl PartialEq<IdentityDID> for &str {
431 fn eq(&self, other: &IdentityDID) -> bool {
432 *self == other.0
433 }
434}
435
436impl FromStr for CanonicalDid {
437 type Err = DidParseError;
438
439 fn from_str(s: &str) -> Result<Self, Self::Err> {
440 Self::parse(s)
441 }
442}
443
444pub fn signer_hex_to_did(hex_key: &str) -> Result<CanonicalDid, DidConversionError> {
458 signer_hex_to_did_with_curve(hex_key, auths_crypto::CurveType::P256)
459}
460
461pub fn signer_hex_to_did_with_curve(
469 hex_key: &str,
470 curve: auths_crypto::CurveType,
471) -> Result<CanonicalDid, DidConversionError> {
472 let bytes = hex::decode(hex_key).map_err(|e| DidConversionError::InvalidHex(e.to_string()))?;
473 let expected = curve.public_key_len();
474 if bytes.len() != expected {
475 return Err(DidConversionError::WrongKeyLength(bytes.len()));
476 }
477 Ok(CanonicalDid::from_public_key_did_key(&bytes, curve))
478}
479
480pub fn validate_did(did_str: &str) -> bool {
484 if let Some(rest) = did_str.strip_prefix("did:keri:") {
485 !rest.is_empty()
486 } else if let Some(rest) = did_str.strip_prefix("did:key:") {
487 !rest.is_empty()
488 } else {
489 false
490 }
491}
492
493#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
495pub enum DidConversionError {
496 #[error("invalid hex: {0}")]
498 InvalidHex(String),
499 #[error("expected 32-byte Ed25519 key, got {0} bytes")]
501 WrongKeyLength(usize),
502}
503
504#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
506#[non_exhaustive]
507pub enum DidParseError {
508 #[error("did:key: DID must start with 'did:key:z', got: {0}")]
510 InvalidDevicePrefix(String),
511 #[error("IdentityDID must start with 'did:keri:', got: {0}")]
513 InvalidIdentityPrefix(String),
514 #[error("DID method-specific identifier is empty")]
516 EmptyIdentifier,
517 #[error("{0}")]
519 InvalidFormat(String),
520 #[error("DID contains control characters")]
522 ControlCharacters,
523}
524
525#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
540#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
541#[serde(transparent)]
542pub struct CanonicalDid(String);
543
544impl CanonicalDid {
545 pub fn parse(raw: &str) -> Result<Self, DidParseError> {
547 if raw.chars().any(|c| c.is_control()) {
548 return Err(DidParseError::ControlCharacters);
549 }
550 let trimmed = raw.trim();
551 if trimmed.is_empty() {
552 return Err(DidParseError::EmptyIdentifier);
553 }
554 let parts: Vec<&str> = trimmed.splitn(3, ':').collect();
555 if parts.len() < 3 || parts[0] != "did" || parts[1].is_empty() || parts[2].is_empty() {
556 return Err(DidParseError::InvalidFormat(format!(
557 "invalid DID format: '{}'",
558 trimmed
559 )));
560 }
561 let canonical = format!("did:{}:{}", parts[1].to_lowercase(), parts[2]);
562 Ok(Self(canonical))
563 }
564
565 pub fn new_unchecked<S: Into<String>>(s: S) -> Self {
567 Self(s.into())
568 }
569
570 pub fn as_str(&self) -> &str {
572 &self.0
573 }
574
575 pub fn method_specific_id(&self) -> &str {
577 self.0.splitn(3, ':').nth(2).unwrap_or("")
578 }
579
580 pub fn ref_name(&self) -> String {
583 self.0
584 .chars()
585 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
586 .collect()
587 }
588
589 pub fn matches_sanitized_ref(&self, ref_name: &str) -> bool {
591 self.ref_name() == ref_name
592 }
593
594 pub fn from_sanitized<'a>(sanitized: &str, known_dids: &'a [Self]) -> Option<&'a Self> {
597 known_dids.iter().find(|did| did.ref_name() == sanitized)
598 }
599
600 pub fn from_public_key_did_key(pubkey: &[u8], curve: auths_crypto::CurveType) -> Self {
614 let varint: &[u8] = match curve {
615 auths_crypto::CurveType::Ed25519 => &[0xED, 0x01],
616 auths_crypto::CurveType::P256 => &[0x80, 0x24],
617 };
618 let mut prefixed = Vec::with_capacity(varint.len() + pubkey.len());
619 prefixed.extend_from_slice(varint);
620 prefixed.extend_from_slice(pubkey);
621 let encoded = bs58::encode(prefixed).into_string();
622 Self(format!("did:key:z{}", encoded))
623 }
624
625 pub fn require_keri(self) -> Result<Self, DidParseError> {
627 let parts: Vec<&str> = self.0.splitn(3, ':').collect();
628 if parts[1] != "keri" {
629 return Err(DidParseError::InvalidFormat(format!(
630 "expected did:keri: DID, got did:{}:",
631 parts[1]
632 )));
633 }
634 let id = parts[2];
635 if id.len() < 2 || id.len() > 128 {
636 return Err(DidParseError::InvalidFormat(
637 "invalid KERI prefix: length must be 2–128 characters".into(),
638 ));
639 }
640 if !id.starts_with(|c: char| c.is_ascii_uppercase()) {
641 return Err(DidParseError::InvalidFormat(format!(
642 "invalid KERI prefix: must start with an uppercase derivation code, got '{}'",
643 &id[..1]
644 )));
645 }
646 Ok(self)
647 }
648
649 pub fn into_inner(self) -> String {
651 self.0
652 }
653}
654
655impl TryFrom<String> for CanonicalDid {
656 type Error = DidParseError;
657 fn try_from(s: String) -> Result<Self, Self::Error> {
658 Self::parse(&s)
659 }
660}
661
662impl TryFrom<&str> for CanonicalDid {
663 type Error = DidParseError;
664 fn try_from(s: &str) -> Result<Self, Self::Error> {
665 Self::parse(s)
666 }
667}
668
669impl From<CanonicalDid> for String {
670 fn from(d: CanonicalDid) -> Self {
671 d.0
672 }
673}
674
675impl fmt::Display for CanonicalDid {
676 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
677 f.write_str(&self.0)
678 }
679}
680
681impl Deref for CanonicalDid {
682 type Target = str;
683 fn deref(&self) -> &str {
684 &self.0
685 }
686}
687
688impl AsRef<str> for CanonicalDid {
689 fn as_ref(&self) -> &str {
690 &self.0
691 }
692}
693
694impl Borrow<str> for CanonicalDid {
695 fn borrow(&self) -> &str {
696 &self.0
697 }
698}
699
700impl<'de> serde::Deserialize<'de> for CanonicalDid {
701 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
702 where
703 D: serde::Deserializer<'de>,
704 {
705 let s = String::deserialize(deserializer)?;
706 Self::parse(&s).map_err(serde::de::Error::custom)
707 }
708}
709
710impl PartialEq<str> for CanonicalDid {
711 fn eq(&self, other: &str) -> bool {
712 self.0 == other
713 }
714}
715
716impl PartialEq<&str> for CanonicalDid {
717 fn eq(&self, other: &&str) -> bool {
718 self.0 == *other
719 }
720}
721
722impl From<IdentityDID> for CanonicalDid {
723 fn from(did: IdentityDID) -> Self {
724 Self(did.into_inner())
725 }
726}
727
728#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
744#[serde(rename_all = "snake_case")]
745#[non_exhaustive]
746pub enum AssuranceLevel {
747 SelfAsserted,
749 TokenVerified,
751 Authenticated,
753 Sovereign,
755}
756
757#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
759#[error(
760 "invalid assurance level '{0}': expected one of: sovereign, authenticated, token_verified, self_asserted"
761)]
762pub struct AssuranceLevelParseError(pub String);
763
764impl AssuranceLevel {
765 pub fn label(&self) -> &'static str {
767 match self {
768 Self::SelfAsserted => "Self-Asserted",
769 Self::TokenVerified => "Token-Verified",
770 Self::Authenticated => "Authenticated",
771 Self::Sovereign => "Sovereign",
772 }
773 }
774
775 pub fn score(&self) -> u8 {
777 match self {
778 Self::SelfAsserted => 1,
779 Self::TokenVerified => 2,
780 Self::Authenticated => 3,
781 Self::Sovereign => 4,
782 }
783 }
784}
785
786impl fmt::Display for AssuranceLevel {
787 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788 f.write_str(self.label())
789 }
790}
791
792impl FromStr for AssuranceLevel {
793 type Err = AssuranceLevelParseError;
794
795 fn from_str(s: &str) -> Result<Self, Self::Err> {
796 match s.trim().to_lowercase().as_str() {
797 "sovereign" => Ok(Self::Sovereign),
798 "authenticated" => Ok(Self::Authenticated),
799 "token_verified" => Ok(Self::TokenVerified),
800 "self_asserted" => Ok(Self::SelfAsserted),
801 _ => Err(AssuranceLevelParseError(s.to_string())),
802 }
803 }
804}
805
806#[cfg(test)]
807mod tests {
808 use super::*;
809 use auths_keri::{Prefix, Said};
810
811 #[test]
812 fn identity_did_from_validated_prefix() {
813 let prefix = Prefix::new_unchecked("EOrg123".to_string());
814 let did = IdentityDID::try_from(&prefix).expect("non-empty prefix converts");
815 assert_eq!(did.as_str(), "did:keri:EOrg123");
816 }
817
818 #[test]
819 fn identity_did_try_from_rejects_empty_prefix() {
820 let empty = Prefix::default();
821 assert!(matches!(
822 IdentityDID::try_from(&empty),
823 Err(DidParseError::EmptyIdentifier)
824 ));
825 }
826
827 #[test]
828 fn report_without_witness_quorum_deserializes() {
829 let json = r#"{
831 "status": {"type": "Valid"},
832 "chain": [],
833 "warnings": []
834 }"#;
835 let report: VerificationReport = serde_json::from_str(json).unwrap();
836 assert!(report.is_valid());
837 assert!(report.witness_quorum.is_none());
838 }
839
840 #[test]
841 fn insufficient_witnesses_serializes_correctly() {
842 let status = VerificationStatus::InsufficientWitnesses {
843 required: 3,
844 verified: 1,
845 };
846 let json = serde_json::to_string(&status).unwrap();
847 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
848 assert_eq!(parsed["type"], "InsufficientWitnesses");
849 assert_eq!(parsed["required"], 3);
850 assert_eq!(parsed["verified"], 1);
851
852 let roundtripped: VerificationStatus = serde_json::from_str(&json).unwrap();
854 assert_eq!(roundtripped, status);
855 }
856
857 #[test]
858 fn report_with_witness_quorum_roundtrips() {
859 use crate::witness::{WitnessQuorum, WitnessReceiptResult};
860
861 let report = VerificationReport {
862 status: VerificationStatus::Valid,
863 chain: vec![],
864 warnings: vec![],
865 witness_quorum: Some(WitnessQuorum {
866 required: 2,
867 verified: 2,
868 receipts: vec![
869 WitnessReceiptResult {
870 witness_id: "did:key:w1".into(),
871 receipt_said: Said::new_unchecked("EReceipt1".into()),
872 verified: true,
873 },
874 WitnessReceiptResult {
875 witness_id: "did:key:w2".into(),
876 receipt_said: Said::new_unchecked("EReceipt2".into()),
877 verified: true,
878 },
879 ],
880 }),
881 anchored: None,
882 duplicity_warning: None,
883 freshness: Freshness::Unknown,
884 as_of: None,
885 };
886
887 let json = serde_json::to_string(&report).unwrap();
888 let parsed: VerificationReport = serde_json::from_str(&json).unwrap();
889 assert_eq!(report, parsed);
890 assert!(parsed.witness_quorum.is_some());
891 assert_eq!(parsed.witness_quorum.unwrap().verified, 2);
892 }
893
894 #[test]
895 fn pre_freshness_report_json_loads_as_unknown() {
896 let json = r#"{"status":{"type":"Valid"},"chain":[],"warnings":[]}"#;
899 let report: VerificationReport = serde_json::from_str(json).unwrap();
900 assert!(report.is_valid());
901 assert_eq!(report.freshness(), Freshness::Unknown);
902 }
903
904 #[test]
905 fn report_without_witness_quorum_skips_in_json() {
906 let report = VerificationReport::valid(vec![]);
907 let json = serde_json::to_string(&report).unwrap();
908 assert!(!json.contains("witness_quorum"));
910 }
911
912 #[test]
915 fn assurance_level_ordering() {
916 assert!(AssuranceLevel::SelfAsserted < AssuranceLevel::TokenVerified);
917 assert!(AssuranceLevel::TokenVerified < AssuranceLevel::Authenticated);
918 assert!(AssuranceLevel::Authenticated < AssuranceLevel::Sovereign);
919 }
920
921 #[test]
922 fn assurance_level_serde_roundtrip() {
923 let variants = [
924 AssuranceLevel::SelfAsserted,
925 AssuranceLevel::TokenVerified,
926 AssuranceLevel::Authenticated,
927 AssuranceLevel::Sovereign,
928 ];
929 for level in variants {
930 let json = serde_json::to_string(&level).unwrap();
931 let parsed: AssuranceLevel = serde_json::from_str(&json).unwrap();
932 assert_eq!(parsed, level);
933 }
934 }
935
936 #[test]
937 fn assurance_level_serde_snake_case() {
938 assert_eq!(
939 serde_json::to_string(&AssuranceLevel::SelfAsserted).unwrap(),
940 "\"self_asserted\""
941 );
942 assert_eq!(
943 serde_json::to_string(&AssuranceLevel::TokenVerified).unwrap(),
944 "\"token_verified\""
945 );
946 assert_eq!(
947 serde_json::to_string(&AssuranceLevel::Authenticated).unwrap(),
948 "\"authenticated\""
949 );
950 assert_eq!(
951 serde_json::to_string(&AssuranceLevel::Sovereign).unwrap(),
952 "\"sovereign\""
953 );
954 }
955
956 #[test]
957 fn assurance_level_from_str() {
958 assert_eq!(
959 "sovereign".parse::<AssuranceLevel>().unwrap(),
960 AssuranceLevel::Sovereign
961 );
962 assert_eq!(
963 "authenticated".parse::<AssuranceLevel>().unwrap(),
964 AssuranceLevel::Authenticated
965 );
966 assert_eq!(
967 "token_verified".parse::<AssuranceLevel>().unwrap(),
968 AssuranceLevel::TokenVerified
969 );
970 assert_eq!(
971 "self_asserted".parse::<AssuranceLevel>().unwrap(),
972 AssuranceLevel::SelfAsserted
973 );
974 assert!("invalid".parse::<AssuranceLevel>().is_err());
975 }
976
977 #[test]
978 fn assurance_level_from_str_case_insensitive() {
979 assert_eq!(
980 "SOVEREIGN".parse::<AssuranceLevel>().unwrap(),
981 AssuranceLevel::Sovereign
982 );
983 assert_eq!(
984 "Authenticated".parse::<AssuranceLevel>().unwrap(),
985 AssuranceLevel::Authenticated
986 );
987 }
988
989 #[test]
990 fn assurance_level_score() {
991 assert_eq!(AssuranceLevel::SelfAsserted.score(), 1);
992 assert_eq!(AssuranceLevel::TokenVerified.score(), 2);
993 assert_eq!(AssuranceLevel::Authenticated.score(), 3);
994 assert_eq!(AssuranceLevel::Sovereign.score(), 4);
995 }
996
997 #[test]
998 fn assurance_level_display() {
999 assert_eq!(AssuranceLevel::SelfAsserted.to_string(), "Self-Asserted");
1000 assert_eq!(AssuranceLevel::TokenVerified.to_string(), "Token-Verified");
1001 assert_eq!(AssuranceLevel::Authenticated.to_string(), "Authenticated");
1002 assert_eq!(AssuranceLevel::Sovereign.to_string(), "Sovereign");
1003 }
1004}