1use crate::witness::WitnessQuorum;
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub struct VerificationReport {
14 pub status: VerificationStatus,
16 pub chain: Vec<ChainLink>,
18 pub warnings: Vec<String>,
20 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub witness_quorum: Option<WitnessQuorum>,
23}
24
25impl VerificationReport {
26 pub fn is_valid(&self) -> bool {
28 matches!(self.status, VerificationStatus::Valid)
29 }
30
31 pub fn valid(chain: Vec<ChainLink>) -> Self {
33 Self {
34 status: VerificationStatus::Valid,
35 chain,
36 warnings: Vec::new(),
37 witness_quorum: None,
38 }
39 }
40
41 pub fn with_status(status: VerificationStatus, chain: Vec<ChainLink>) -> Self {
43 Self {
44 status,
45 chain,
46 warnings: Vec::new(),
47 witness_quorum: None,
48 }
49 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54#[serde(tag = "type")]
55pub enum VerificationStatus {
56 Valid,
58 Expired {
60 at: DateTime<Utc>,
62 },
63 Revoked {
65 at: Option<DateTime<Utc>>,
67 },
68 InvalidSignature {
70 step: usize,
72 },
73 BrokenChain {
75 missing_link: String,
77 },
78 InsufficientWitnesses {
80 required: usize,
82 verified: usize,
84 },
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
89pub struct ChainLink {
90 pub issuer: String,
92 pub subject: String,
94 pub valid: bool,
96 pub error: Option<String>,
98}
99
100impl ChainLink {
101 pub fn valid(issuer: String, subject: String) -> Self {
103 Self {
104 issuer,
105 subject,
106 valid: true,
107 error: None,
108 }
109 }
110
111 pub fn invalid(issuer: String, subject: String, error: String) -> Self {
113 Self {
114 issuer,
115 subject,
116 valid: false,
117 error: Some(error),
118 }
119 }
120}
121
122use std::borrow::Borrow;
127use std::fmt;
128use std::ops::Deref;
129use std::str::FromStr;
130
131#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
146#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
147#[repr(transparent)]
148pub struct IdentityDID(String);
149
150impl IdentityDID {
151 pub fn new_unchecked<S: Into<String>>(s: S) -> Self {
153 Self(s.into())
154 }
155
156 pub fn parse(s: &str) -> Result<Self, DidParseError> {
168 match s.strip_prefix("did:keri:") {
169 Some("") => Err(DidParseError::EmptyIdentifier),
170 Some(_) => Ok(Self(s.to_string())),
171 None => Err(DidParseError::InvalidIdentityPrefix(s.to_string())),
172 }
173 }
174
175 pub fn from_prefix(prefix: &str) -> Result<Self, DidParseError> {
187 if prefix.is_empty() {
188 return Err(DidParseError::EmptyIdentifier);
189 }
190 Ok(Self(format!("did:keri:{}", prefix)))
191 }
192
193 pub fn prefix(&self) -> &str {
202 self.0.strip_prefix("did:keri:").unwrap_or(&self.0)
203 }
204
205 pub fn as_str(&self) -> &str {
207 &self.0
208 }
209
210 pub fn into_inner(self) -> String {
212 self.0
213 }
214}
215
216impl fmt::Display for IdentityDID {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 f.write_str(&self.0)
219 }
220}
221
222impl FromStr for IdentityDID {
223 type Err = DidParseError;
224
225 fn from_str(s: &str) -> Result<Self, Self::Err> {
226 Self::parse(s)
227 }
228}
229
230impl TryFrom<&str> for IdentityDID {
231 type Error = DidParseError;
232
233 fn try_from(s: &str) -> Result<Self, Self::Error> {
234 Self::parse(s)
235 }
236}
237
238impl TryFrom<String> for IdentityDID {
239 type Error = DidParseError;
240
241 fn try_from(s: String) -> Result<Self, Self::Error> {
242 Self::parse(&s)
243 }
244}
245
246impl From<IdentityDID> for String {
247 fn from(did: IdentityDID) -> String {
248 did.0
249 }
250}
251
252impl<'de> serde::Deserialize<'de> for IdentityDID {
253 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
254 where
255 D: serde::Deserializer<'de>,
256 {
257 let s = String::deserialize(deserializer)?;
258 Self::parse(&s).map_err(serde::de::Error::custom)
259 }
260}
261
262impl Deref for IdentityDID {
263 type Target = str;
264
265 fn deref(&self) -> &Self::Target {
266 &self.0
267 }
268}
269
270impl AsRef<str> for IdentityDID {
271 fn as_ref(&self) -> &str {
272 &self.0
273 }
274}
275
276impl Borrow<str> for IdentityDID {
277 fn borrow(&self) -> &str {
278 &self.0
279 }
280}
281
282impl PartialEq<str> for IdentityDID {
283 fn eq(&self, other: &str) -> bool {
284 self.0 == other
285 }
286}
287
288impl PartialEq<&str> for IdentityDID {
289 fn eq(&self, other: &&str) -> bool {
290 self.0 == *other
291 }
292}
293
294impl PartialEq<IdentityDID> for str {
295 fn eq(&self, other: &IdentityDID) -> bool {
296 self == other.0
297 }
298}
299
300impl PartialEq<IdentityDID> for &str {
301 fn eq(&self, other: &IdentityDID) -> bool {
302 *self == other.0
303 }
304}
305
306#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
312#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
313pub struct DeviceDID(String);
314
315impl DeviceDID {
316 pub fn new_unchecked<S: Into<String>>(s: S) -> Self {
318 DeviceDID(s.into())
319 }
320
321 pub fn parse(s: &str) -> Result<Self, DidParseError> {
333 match s.strip_prefix("did:key:z") {
334 Some("") => Err(DidParseError::EmptyIdentifier),
335 Some(_) => Ok(Self(s.to_string())),
336 None => Err(DidParseError::InvalidDevicePrefix(s.to_string())),
337 }
338 }
339
340 pub fn from_ed25519(pubkey: &[u8; 32]) -> Self {
344 let mut prefixed = vec![0xED, 0x01];
345 prefixed.extend_from_slice(pubkey);
346
347 let encoded = bs58::encode(prefixed).into_string();
348 Self(format!("did:key:z{}", encoded))
349 }
350
351 pub fn ref_name(&self) -> String {
354 self.0
355 .chars()
356 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
357 .collect()
358 }
359
360 pub fn matches_sanitized_ref(&self, ref_name: &str) -> bool {
363 self.ref_name() == ref_name
364 }
365
366 pub fn from_sanitized<'a>(
369 sanitized: &str,
370 known_dids: &'a [DeviceDID],
371 ) -> Option<&'a DeviceDID> {
372 known_dids.iter().find(|did| did.ref_name() == sanitized)
373 }
374
375 pub fn as_str(&self) -> &str {
377 &self.0
378 }
379}
380
381impl fmt::Display for DeviceDID {
382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383 self.0.fmt(f)
384 }
385}
386
387impl FromStr for DeviceDID {
388 type Err = DidParseError;
389
390 fn from_str(s: &str) -> Result<Self, Self::Err> {
391 Self::parse(s)
392 }
393}
394
395impl TryFrom<&str> for DeviceDID {
396 type Error = DidParseError;
397
398 fn try_from(s: &str) -> Result<Self, Self::Error> {
399 Self::parse(s)
400 }
401}
402
403impl TryFrom<String> for DeviceDID {
404 type Error = DidParseError;
405
406 fn try_from(s: String) -> Result<Self, Self::Error> {
407 Self::parse(&s)
408 }
409}
410
411impl From<DeviceDID> for String {
412 fn from(did: DeviceDID) -> String {
413 did.0
414 }
415}
416
417impl<'de> serde::Deserialize<'de> for DeviceDID {
418 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
419 where
420 D: serde::Deserializer<'de>,
421 {
422 let s = String::deserialize(deserializer)?;
423 Self::parse(&s).map_err(serde::de::Error::custom)
424 }
425}
426
427impl Deref for DeviceDID {
428 type Target = str;
429
430 fn deref(&self) -> &Self::Target {
431 &self.0
432 }
433}
434
435pub fn signer_hex_to_did(hex_key: &str) -> Result<DeviceDID, DidConversionError> {
449 let bytes = hex::decode(hex_key).map_err(|e| DidConversionError::InvalidHex(e.to_string()))?;
450 let arr: [u8; 32] = bytes
451 .try_into()
452 .map_err(|v: Vec<u8>| DidConversionError::WrongKeyLength(v.len()))?;
453 Ok(DeviceDID::from_ed25519(&arr))
454}
455
456pub fn validate_did(did_str: &str) -> bool {
460 if let Some(rest) = did_str.strip_prefix("did:keri:") {
461 !rest.is_empty()
462 } else if let Some(rest) = did_str.strip_prefix("did:key:") {
463 !rest.is_empty()
464 } else {
465 false
466 }
467}
468
469#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
471pub enum DidConversionError {
472 #[error("invalid hex: {0}")]
474 InvalidHex(String),
475 #[error("expected 32-byte Ed25519 key, got {0} bytes")]
477 WrongKeyLength(usize),
478}
479
480#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
482#[non_exhaustive]
483pub enum DidParseError {
484 #[error("DeviceDID must start with 'did:key:z', got: {0}")]
486 InvalidDevicePrefix(String),
487 #[error("IdentityDID must start with 'did:keri:', got: {0}")]
489 InvalidIdentityPrefix(String),
490 #[error("DID method-specific identifier is empty")]
492 EmptyIdentifier,
493 #[error("{0}")]
495 InvalidFormat(String),
496 #[error("DID contains control characters")]
498 ControlCharacters,
499}
500
501#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
516#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
517#[serde(transparent)]
518pub struct CanonicalDid(String);
519
520impl CanonicalDid {
521 pub fn parse(raw: &str) -> Result<Self, DidParseError> {
523 if raw.chars().any(|c| c.is_control()) {
524 return Err(DidParseError::ControlCharacters);
525 }
526 let trimmed = raw.trim();
527 if trimmed.is_empty() {
528 return Err(DidParseError::EmptyIdentifier);
529 }
530 let parts: Vec<&str> = trimmed.splitn(3, ':').collect();
531 if parts.len() < 3 || parts[0] != "did" || parts[1].is_empty() || parts[2].is_empty() {
532 return Err(DidParseError::InvalidFormat(format!(
533 "invalid DID format: '{}'",
534 trimmed
535 )));
536 }
537 let canonical = format!("did:{}:{}", parts[1].to_lowercase(), parts[2]);
538 Ok(Self(canonical))
539 }
540
541 pub fn new_unchecked<S: Into<String>>(s: S) -> Self {
543 Self(s.into())
544 }
545
546 pub fn as_str(&self) -> &str {
548 &self.0
549 }
550
551 pub fn method_specific_id(&self) -> &str {
553 self.0.splitn(3, ':').nth(2).unwrap_or("")
554 }
555
556 pub fn require_keri(self) -> Result<Self, DidParseError> {
558 let parts: Vec<&str> = self.0.splitn(3, ':').collect();
559 if parts[1] != "keri" {
560 return Err(DidParseError::InvalidFormat(format!(
561 "expected did:keri: DID, got did:{}:",
562 parts[1]
563 )));
564 }
565 let id = parts[2];
566 if id.len() < 2 || id.len() > 128 {
567 return Err(DidParseError::InvalidFormat(
568 "invalid KERI prefix: length must be 2–128 characters".into(),
569 ));
570 }
571 if !id.starts_with(|c: char| c.is_ascii_uppercase()) {
572 return Err(DidParseError::InvalidFormat(format!(
573 "invalid KERI prefix: must start with an uppercase derivation code, got '{}'",
574 &id[..1]
575 )));
576 }
577 Ok(self)
578 }
579
580 pub fn into_inner(self) -> String {
582 self.0
583 }
584}
585
586impl TryFrom<String> for CanonicalDid {
587 type Error = DidParseError;
588 fn try_from(s: String) -> Result<Self, Self::Error> {
589 Self::parse(&s)
590 }
591}
592
593impl TryFrom<&str> for CanonicalDid {
594 type Error = DidParseError;
595 fn try_from(s: &str) -> Result<Self, Self::Error> {
596 Self::parse(s)
597 }
598}
599
600impl From<CanonicalDid> for String {
601 fn from(d: CanonicalDid) -> Self {
602 d.0
603 }
604}
605
606impl fmt::Display for CanonicalDid {
607 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608 f.write_str(&self.0)
609 }
610}
611
612impl Deref for CanonicalDid {
613 type Target = str;
614 fn deref(&self) -> &str {
615 &self.0
616 }
617}
618
619impl AsRef<str> for CanonicalDid {
620 fn as_ref(&self) -> &str {
621 &self.0
622 }
623}
624
625impl Borrow<str> for CanonicalDid {
626 fn borrow(&self) -> &str {
627 &self.0
628 }
629}
630
631impl<'de> serde::Deserialize<'de> for CanonicalDid {
632 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
633 where
634 D: serde::Deserializer<'de>,
635 {
636 let s = String::deserialize(deserializer)?;
637 Self::parse(&s).map_err(serde::de::Error::custom)
638 }
639}
640
641impl PartialEq<str> for CanonicalDid {
642 fn eq(&self, other: &str) -> bool {
643 self.0 == other
644 }
645}
646
647impl PartialEq<&str> for CanonicalDid {
648 fn eq(&self, other: &&str) -> bool {
649 self.0 == *other
650 }
651}
652
653impl From<IdentityDID> for CanonicalDid {
654 fn from(did: IdentityDID) -> Self {
655 Self(did.into_inner())
656 }
657}
658
659impl From<DeviceDID> for CanonicalDid {
660 fn from(did: DeviceDID) -> Self {
661 Self(did.0)
662 }
663}
664
665#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
681#[serde(rename_all = "snake_case")]
682#[non_exhaustive]
683pub enum AssuranceLevel {
684 SelfAsserted,
686 TokenVerified,
688 Authenticated,
690 Sovereign,
692}
693
694#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
696#[error(
697 "invalid assurance level '{0}': expected one of: sovereign, authenticated, token_verified, self_asserted"
698)]
699pub struct AssuranceLevelParseError(pub String);
700
701impl AssuranceLevel {
702 pub fn label(&self) -> &'static str {
704 match self {
705 Self::SelfAsserted => "Self-Asserted",
706 Self::TokenVerified => "Token-Verified",
707 Self::Authenticated => "Authenticated",
708 Self::Sovereign => "Sovereign",
709 }
710 }
711
712 pub fn score(&self) -> u8 {
714 match self {
715 Self::SelfAsserted => 1,
716 Self::TokenVerified => 2,
717 Self::Authenticated => 3,
718 Self::Sovereign => 4,
719 }
720 }
721}
722
723impl fmt::Display for AssuranceLevel {
724 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
725 f.write_str(self.label())
726 }
727}
728
729impl FromStr for AssuranceLevel {
730 type Err = AssuranceLevelParseError;
731
732 fn from_str(s: &str) -> Result<Self, Self::Err> {
733 match s.trim().to_lowercase().as_str() {
734 "sovereign" => Ok(Self::Sovereign),
735 "authenticated" => Ok(Self::Authenticated),
736 "token_verified" => Ok(Self::TokenVerified),
737 "self_asserted" => Ok(Self::SelfAsserted),
738 _ => Err(AssuranceLevelParseError(s.to_string())),
739 }
740 }
741}
742
743#[cfg(test)]
744mod tests {
745 use super::*;
746 use crate::keri::Said;
747
748 #[test]
749 fn report_without_witness_quorum_deserializes() {
750 let json = r#"{
752 "status": {"type": "Valid"},
753 "chain": [],
754 "warnings": []
755 }"#;
756 let report: VerificationReport = serde_json::from_str(json).unwrap();
757 assert!(report.is_valid());
758 assert!(report.witness_quorum.is_none());
759 }
760
761 #[test]
762 fn insufficient_witnesses_serializes_correctly() {
763 let status = VerificationStatus::InsufficientWitnesses {
764 required: 3,
765 verified: 1,
766 };
767 let json = serde_json::to_string(&status).unwrap();
768 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
769 assert_eq!(parsed["type"], "InsufficientWitnesses");
770 assert_eq!(parsed["required"], 3);
771 assert_eq!(parsed["verified"], 1);
772
773 let roundtripped: VerificationStatus = serde_json::from_str(&json).unwrap();
775 assert_eq!(roundtripped, status);
776 }
777
778 #[test]
779 fn report_with_witness_quorum_roundtrips() {
780 use crate::witness::{WitnessQuorum, WitnessReceiptResult};
781
782 let report = VerificationReport {
783 status: VerificationStatus::Valid,
784 chain: vec![],
785 warnings: vec![],
786 witness_quorum: Some(WitnessQuorum {
787 required: 2,
788 verified: 2,
789 receipts: vec![
790 WitnessReceiptResult {
791 witness_id: "did:key:w1".into(),
792 receipt_said: Said::new_unchecked("EReceipt1".into()),
793 verified: true,
794 },
795 WitnessReceiptResult {
796 witness_id: "did:key:w2".into(),
797 receipt_said: Said::new_unchecked("EReceipt2".into()),
798 verified: true,
799 },
800 ],
801 }),
802 };
803
804 let json = serde_json::to_string(&report).unwrap();
805 let parsed: VerificationReport = serde_json::from_str(&json).unwrap();
806 assert_eq!(report, parsed);
807 assert!(parsed.witness_quorum.is_some());
808 assert_eq!(parsed.witness_quorum.unwrap().verified, 2);
809 }
810
811 #[test]
812 fn report_without_witness_quorum_skips_in_json() {
813 let report = VerificationReport::valid(vec![]);
814 let json = serde_json::to_string(&report).unwrap();
815 assert!(!json.contains("witness_quorum"));
817 }
818
819 #[test]
822 fn assurance_level_ordering() {
823 assert!(AssuranceLevel::SelfAsserted < AssuranceLevel::TokenVerified);
824 assert!(AssuranceLevel::TokenVerified < AssuranceLevel::Authenticated);
825 assert!(AssuranceLevel::Authenticated < AssuranceLevel::Sovereign);
826 }
827
828 #[test]
829 fn assurance_level_serde_roundtrip() {
830 let variants = [
831 AssuranceLevel::SelfAsserted,
832 AssuranceLevel::TokenVerified,
833 AssuranceLevel::Authenticated,
834 AssuranceLevel::Sovereign,
835 ];
836 for level in variants {
837 let json = serde_json::to_string(&level).unwrap();
838 let parsed: AssuranceLevel = serde_json::from_str(&json).unwrap();
839 assert_eq!(parsed, level);
840 }
841 }
842
843 #[test]
844 fn assurance_level_serde_snake_case() {
845 assert_eq!(
846 serde_json::to_string(&AssuranceLevel::SelfAsserted).unwrap(),
847 "\"self_asserted\""
848 );
849 assert_eq!(
850 serde_json::to_string(&AssuranceLevel::TokenVerified).unwrap(),
851 "\"token_verified\""
852 );
853 assert_eq!(
854 serde_json::to_string(&AssuranceLevel::Authenticated).unwrap(),
855 "\"authenticated\""
856 );
857 assert_eq!(
858 serde_json::to_string(&AssuranceLevel::Sovereign).unwrap(),
859 "\"sovereign\""
860 );
861 }
862
863 #[test]
864 fn assurance_level_from_str() {
865 assert_eq!(
866 "sovereign".parse::<AssuranceLevel>().unwrap(),
867 AssuranceLevel::Sovereign
868 );
869 assert_eq!(
870 "authenticated".parse::<AssuranceLevel>().unwrap(),
871 AssuranceLevel::Authenticated
872 );
873 assert_eq!(
874 "token_verified".parse::<AssuranceLevel>().unwrap(),
875 AssuranceLevel::TokenVerified
876 );
877 assert_eq!(
878 "self_asserted".parse::<AssuranceLevel>().unwrap(),
879 AssuranceLevel::SelfAsserted
880 );
881 assert!("invalid".parse::<AssuranceLevel>().is_err());
882 }
883
884 #[test]
885 fn assurance_level_from_str_case_insensitive() {
886 assert_eq!(
887 "SOVEREIGN".parse::<AssuranceLevel>().unwrap(),
888 AssuranceLevel::Sovereign
889 );
890 assert_eq!(
891 "Authenticated".parse::<AssuranceLevel>().unwrap(),
892 AssuranceLevel::Authenticated
893 );
894 }
895
896 #[test]
897 fn assurance_level_score() {
898 assert_eq!(AssuranceLevel::SelfAsserted.score(), 1);
899 assert_eq!(AssuranceLevel::TokenVerified.score(), 2);
900 assert_eq!(AssuranceLevel::Authenticated.score(), 3);
901 assert_eq!(AssuranceLevel::Sovereign.score(), 4);
902 }
903
904 #[test]
905 fn assurance_level_display() {
906 assert_eq!(AssuranceLevel::SelfAsserted.to_string(), "Self-Asserted");
907 assert_eq!(AssuranceLevel::TokenVerified.to_string(), "Token-Verified");
908 assert_eq!(AssuranceLevel::Authenticated.to_string(), "Authenticated");
909 assert_eq!(AssuranceLevel::Sovereign.to_string(), "Sovereign");
910 }
911}