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 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub anchored: Option<auths_keri::AnchorStatus>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub duplicity_warning: Option<crate::duplicity::DuplicityReport>,
37}
38
39impl VerificationReport {
40 pub fn is_valid(&self) -> bool {
42 matches!(self.status, VerificationStatus::Valid)
43 }
44
45 pub fn valid(chain: Vec<ChainLink>) -> Self {
47 Self {
48 status: VerificationStatus::Valid,
49 chain,
50 warnings: Vec::new(),
51 witness_quorum: None,
52 anchored: None,
53 duplicity_warning: None,
54 }
55 }
56
57 pub fn with_status(status: VerificationStatus, chain: Vec<ChainLink>) -> Self {
59 Self {
60 status,
61 chain,
62 warnings: Vec::new(),
63 witness_quorum: None,
64 anchored: None,
65 duplicity_warning: None,
66 }
67 }
68
69 pub fn with_duplicity_warning(mut self, warning: crate::duplicity::DuplicityReport) -> Self {
73 self.duplicity_warning = Some(warning);
74 self
75 }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
80#[serde(tag = "type")]
81pub enum VerificationStatus {
82 Valid,
84 Expired {
86 at: DateTime<Utc>,
88 },
89 Revoked {
91 at: Option<DateTime<Utc>>,
93 },
94 InvalidSignature {
96 step: usize,
98 },
99 BrokenChain {
101 missing_link: String,
103 },
104 InsufficientWitnesses {
106 required: usize,
108 verified: usize,
110 },
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
115pub struct ChainLink {
116 pub issuer: String,
118 pub subject: String,
120 pub valid: bool,
122 pub error: Option<String>,
124}
125
126impl ChainLink {
127 pub fn valid(issuer: String, subject: String) -> Self {
129 Self {
130 issuer,
131 subject,
132 valid: true,
133 error: None,
134 }
135 }
136
137 pub fn invalid(issuer: String, subject: String, error: String) -> Self {
139 Self {
140 issuer,
141 subject,
142 valid: false,
143 error: Some(error),
144 }
145 }
146}
147
148use std::borrow::Borrow;
153use std::fmt;
154use std::ops::Deref;
155use std::str::FromStr;
156
157#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
172#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
173#[repr(transparent)]
174pub struct IdentityDID(String);
175
176impl IdentityDID {
177 pub fn new_unchecked<S: Into<String>>(s: S) -> Self {
179 Self(s.into())
180 }
181
182 pub fn parse(s: &str) -> Result<Self, DidParseError> {
194 match s.strip_prefix("did:keri:") {
195 Some("") => Err(DidParseError::EmptyIdentifier),
196 Some(_) => Ok(Self(s.to_string())),
197 None => Err(DidParseError::InvalidIdentityPrefix(s.to_string())),
198 }
199 }
200
201 pub fn from_prefix(prefix: &str) -> Result<Self, DidParseError> {
213 if prefix.is_empty() {
214 return Err(DidParseError::EmptyIdentifier);
215 }
216 Ok(Self(format!("did:keri:{}", prefix)))
217 }
218
219 pub fn prefix(&self) -> &str {
228 self.0.strip_prefix("did:keri:").unwrap_or(&self.0)
229 }
230
231 pub fn as_str(&self) -> &str {
233 &self.0
234 }
235
236 pub fn into_inner(self) -> String {
238 self.0
239 }
240}
241
242impl fmt::Display for IdentityDID {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 f.write_str(&self.0)
245 }
246}
247
248impl FromStr for IdentityDID {
249 type Err = DidParseError;
250
251 fn from_str(s: &str) -> Result<Self, Self::Err> {
252 Self::parse(s)
253 }
254}
255
256impl TryFrom<&str> for IdentityDID {
257 type Error = DidParseError;
258
259 fn try_from(s: &str) -> Result<Self, Self::Error> {
260 Self::parse(s)
261 }
262}
263
264impl TryFrom<String> for IdentityDID {
265 type Error = DidParseError;
266
267 fn try_from(s: String) -> Result<Self, Self::Error> {
268 Self::parse(&s)
269 }
270}
271
272impl From<IdentityDID> for String {
273 fn from(did: IdentityDID) -> String {
274 did.0
275 }
276}
277
278impl<'de> serde::Deserialize<'de> for IdentityDID {
279 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
280 where
281 D: serde::Deserializer<'de>,
282 {
283 let s = String::deserialize(deserializer)?;
284 Self::parse(&s).map_err(serde::de::Error::custom)
285 }
286}
287
288impl Deref for IdentityDID {
289 type Target = str;
290
291 fn deref(&self) -> &Self::Target {
292 &self.0
293 }
294}
295
296impl AsRef<str> for IdentityDID {
297 fn as_ref(&self) -> &str {
298 &self.0
299 }
300}
301
302impl Borrow<str> for IdentityDID {
303 fn borrow(&self) -> &str {
304 &self.0
305 }
306}
307
308impl PartialEq<str> for IdentityDID {
309 fn eq(&self, other: &str) -> bool {
310 self.0 == other
311 }
312}
313
314impl PartialEq<&str> for IdentityDID {
315 fn eq(&self, other: &&str) -> bool {
316 self.0 == *other
317 }
318}
319
320impl PartialEq<IdentityDID> for str {
321 fn eq(&self, other: &IdentityDID) -> bool {
322 self == other.0
323 }
324}
325
326impl PartialEq<IdentityDID> for &str {
327 fn eq(&self, other: &IdentityDID) -> bool {
328 *self == other.0
329 }
330}
331
332impl FromStr for CanonicalDid {
333 type Err = DidParseError;
334
335 fn from_str(s: &str) -> Result<Self, Self::Err> {
336 Self::parse(s)
337 }
338}
339
340pub fn signer_hex_to_did(hex_key: &str) -> Result<CanonicalDid, DidConversionError> {
354 signer_hex_to_did_with_curve(hex_key, auths_crypto::CurveType::P256)
355}
356
357pub fn signer_hex_to_did_with_curve(
365 hex_key: &str,
366 curve: auths_crypto::CurveType,
367) -> Result<CanonicalDid, DidConversionError> {
368 let bytes = hex::decode(hex_key).map_err(|e| DidConversionError::InvalidHex(e.to_string()))?;
369 let expected = curve.public_key_len();
370 if bytes.len() != expected {
371 return Err(DidConversionError::WrongKeyLength(bytes.len()));
372 }
373 Ok(CanonicalDid::from_public_key_did_key(&bytes, curve))
374}
375
376pub fn validate_did(did_str: &str) -> bool {
380 if let Some(rest) = did_str.strip_prefix("did:keri:") {
381 !rest.is_empty()
382 } else if let Some(rest) = did_str.strip_prefix("did:key:") {
383 !rest.is_empty()
384 } else {
385 false
386 }
387}
388
389#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
391pub enum DidConversionError {
392 #[error("invalid hex: {0}")]
394 InvalidHex(String),
395 #[error("expected 32-byte Ed25519 key, got {0} bytes")]
397 WrongKeyLength(usize),
398}
399
400#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
402#[non_exhaustive]
403pub enum DidParseError {
404 #[error("did:key: DID must start with 'did:key:z', got: {0}")]
406 InvalidDevicePrefix(String),
407 #[error("IdentityDID must start with 'did:keri:', got: {0}")]
409 InvalidIdentityPrefix(String),
410 #[error("DID method-specific identifier is empty")]
412 EmptyIdentifier,
413 #[error("{0}")]
415 InvalidFormat(String),
416 #[error("DID contains control characters")]
418 ControlCharacters,
419}
420
421#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
436#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
437#[serde(transparent)]
438pub struct CanonicalDid(String);
439
440impl CanonicalDid {
441 pub fn parse(raw: &str) -> Result<Self, DidParseError> {
443 if raw.chars().any(|c| c.is_control()) {
444 return Err(DidParseError::ControlCharacters);
445 }
446 let trimmed = raw.trim();
447 if trimmed.is_empty() {
448 return Err(DidParseError::EmptyIdentifier);
449 }
450 let parts: Vec<&str> = trimmed.splitn(3, ':').collect();
451 if parts.len() < 3 || parts[0] != "did" || parts[1].is_empty() || parts[2].is_empty() {
452 return Err(DidParseError::InvalidFormat(format!(
453 "invalid DID format: '{}'",
454 trimmed
455 )));
456 }
457 let canonical = format!("did:{}:{}", parts[1].to_lowercase(), parts[2]);
458 Ok(Self(canonical))
459 }
460
461 pub fn new_unchecked<S: Into<String>>(s: S) -> Self {
463 Self(s.into())
464 }
465
466 pub fn as_str(&self) -> &str {
468 &self.0
469 }
470
471 pub fn method_specific_id(&self) -> &str {
473 self.0.splitn(3, ':').nth(2).unwrap_or("")
474 }
475
476 pub fn ref_name(&self) -> String {
479 self.0
480 .chars()
481 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
482 .collect()
483 }
484
485 pub fn matches_sanitized_ref(&self, ref_name: &str) -> bool {
487 self.ref_name() == ref_name
488 }
489
490 pub fn from_sanitized<'a>(sanitized: &str, known_dids: &'a [Self]) -> Option<&'a Self> {
493 known_dids.iter().find(|did| did.ref_name() == sanitized)
494 }
495
496 pub fn from_public_key_did_key(pubkey: &[u8], curve: auths_crypto::CurveType) -> Self {
510 let varint: &[u8] = match curve {
511 auths_crypto::CurveType::Ed25519 => &[0xED, 0x01],
512 auths_crypto::CurveType::P256 => &[0x80, 0x24],
513 };
514 let mut prefixed = Vec::with_capacity(varint.len() + pubkey.len());
515 prefixed.extend_from_slice(varint);
516 prefixed.extend_from_slice(pubkey);
517 let encoded = bs58::encode(prefixed).into_string();
518 Self(format!("did:key:z{}", encoded))
519 }
520
521 pub fn require_keri(self) -> Result<Self, DidParseError> {
523 let parts: Vec<&str> = self.0.splitn(3, ':').collect();
524 if parts[1] != "keri" {
525 return Err(DidParseError::InvalidFormat(format!(
526 "expected did:keri: DID, got did:{}:",
527 parts[1]
528 )));
529 }
530 let id = parts[2];
531 if id.len() < 2 || id.len() > 128 {
532 return Err(DidParseError::InvalidFormat(
533 "invalid KERI prefix: length must be 2–128 characters".into(),
534 ));
535 }
536 if !id.starts_with(|c: char| c.is_ascii_uppercase()) {
537 return Err(DidParseError::InvalidFormat(format!(
538 "invalid KERI prefix: must start with an uppercase derivation code, got '{}'",
539 &id[..1]
540 )));
541 }
542 Ok(self)
543 }
544
545 pub fn into_inner(self) -> String {
547 self.0
548 }
549}
550
551impl TryFrom<String> for CanonicalDid {
552 type Error = DidParseError;
553 fn try_from(s: String) -> Result<Self, Self::Error> {
554 Self::parse(&s)
555 }
556}
557
558impl TryFrom<&str> for CanonicalDid {
559 type Error = DidParseError;
560 fn try_from(s: &str) -> Result<Self, Self::Error> {
561 Self::parse(s)
562 }
563}
564
565impl From<CanonicalDid> for String {
566 fn from(d: CanonicalDid) -> Self {
567 d.0
568 }
569}
570
571impl fmt::Display for CanonicalDid {
572 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
573 f.write_str(&self.0)
574 }
575}
576
577impl Deref for CanonicalDid {
578 type Target = str;
579 fn deref(&self) -> &str {
580 &self.0
581 }
582}
583
584impl AsRef<str> for CanonicalDid {
585 fn as_ref(&self) -> &str {
586 &self.0
587 }
588}
589
590impl Borrow<str> for CanonicalDid {
591 fn borrow(&self) -> &str {
592 &self.0
593 }
594}
595
596impl<'de> serde::Deserialize<'de> for CanonicalDid {
597 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
598 where
599 D: serde::Deserializer<'de>,
600 {
601 let s = String::deserialize(deserializer)?;
602 Self::parse(&s).map_err(serde::de::Error::custom)
603 }
604}
605
606impl PartialEq<str> for CanonicalDid {
607 fn eq(&self, other: &str) -> bool {
608 self.0 == other
609 }
610}
611
612impl PartialEq<&str> for CanonicalDid {
613 fn eq(&self, other: &&str) -> bool {
614 self.0 == *other
615 }
616}
617
618impl From<IdentityDID> for CanonicalDid {
619 fn from(did: IdentityDID) -> Self {
620 Self(did.into_inner())
621 }
622}
623
624#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
640#[serde(rename_all = "snake_case")]
641#[non_exhaustive]
642pub enum AssuranceLevel {
643 SelfAsserted,
645 TokenVerified,
647 Authenticated,
649 Sovereign,
651}
652
653#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
655#[error(
656 "invalid assurance level '{0}': expected one of: sovereign, authenticated, token_verified, self_asserted"
657)]
658pub struct AssuranceLevelParseError(pub String);
659
660impl AssuranceLevel {
661 pub fn label(&self) -> &'static str {
663 match self {
664 Self::SelfAsserted => "Self-Asserted",
665 Self::TokenVerified => "Token-Verified",
666 Self::Authenticated => "Authenticated",
667 Self::Sovereign => "Sovereign",
668 }
669 }
670
671 pub fn score(&self) -> u8 {
673 match self {
674 Self::SelfAsserted => 1,
675 Self::TokenVerified => 2,
676 Self::Authenticated => 3,
677 Self::Sovereign => 4,
678 }
679 }
680}
681
682impl fmt::Display for AssuranceLevel {
683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684 f.write_str(self.label())
685 }
686}
687
688impl FromStr for AssuranceLevel {
689 type Err = AssuranceLevelParseError;
690
691 fn from_str(s: &str) -> Result<Self, Self::Err> {
692 match s.trim().to_lowercase().as_str() {
693 "sovereign" => Ok(Self::Sovereign),
694 "authenticated" => Ok(Self::Authenticated),
695 "token_verified" => Ok(Self::TokenVerified),
696 "self_asserted" => Ok(Self::SelfAsserted),
697 _ => Err(AssuranceLevelParseError(s.to_string())),
698 }
699 }
700}
701
702#[cfg(test)]
703mod tests {
704 use super::*;
705 use auths_keri::Said;
706
707 #[test]
708 fn report_without_witness_quorum_deserializes() {
709 let json = r#"{
711 "status": {"type": "Valid"},
712 "chain": [],
713 "warnings": []
714 }"#;
715 let report: VerificationReport = serde_json::from_str(json).unwrap();
716 assert!(report.is_valid());
717 assert!(report.witness_quorum.is_none());
718 }
719
720 #[test]
721 fn insufficient_witnesses_serializes_correctly() {
722 let status = VerificationStatus::InsufficientWitnesses {
723 required: 3,
724 verified: 1,
725 };
726 let json = serde_json::to_string(&status).unwrap();
727 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
728 assert_eq!(parsed["type"], "InsufficientWitnesses");
729 assert_eq!(parsed["required"], 3);
730 assert_eq!(parsed["verified"], 1);
731
732 let roundtripped: VerificationStatus = serde_json::from_str(&json).unwrap();
734 assert_eq!(roundtripped, status);
735 }
736
737 #[test]
738 fn report_with_witness_quorum_roundtrips() {
739 use crate::witness::{WitnessQuorum, WitnessReceiptResult};
740
741 let report = VerificationReport {
742 status: VerificationStatus::Valid,
743 chain: vec![],
744 warnings: vec![],
745 witness_quorum: Some(WitnessQuorum {
746 required: 2,
747 verified: 2,
748 receipts: vec![
749 WitnessReceiptResult {
750 witness_id: "did:key:w1".into(),
751 receipt_said: Said::new_unchecked("EReceipt1".into()),
752 verified: true,
753 },
754 WitnessReceiptResult {
755 witness_id: "did:key:w2".into(),
756 receipt_said: Said::new_unchecked("EReceipt2".into()),
757 verified: true,
758 },
759 ],
760 }),
761 anchored: None,
762 duplicity_warning: None,
763 };
764
765 let json = serde_json::to_string(&report).unwrap();
766 let parsed: VerificationReport = serde_json::from_str(&json).unwrap();
767 assert_eq!(report, parsed);
768 assert!(parsed.witness_quorum.is_some());
769 assert_eq!(parsed.witness_quorum.unwrap().verified, 2);
770 }
771
772 #[test]
773 fn report_without_witness_quorum_skips_in_json() {
774 let report = VerificationReport::valid(vec![]);
775 let json = serde_json::to_string(&report).unwrap();
776 assert!(!json.contains("witness_quorum"));
778 }
779
780 #[test]
783 fn assurance_level_ordering() {
784 assert!(AssuranceLevel::SelfAsserted < AssuranceLevel::TokenVerified);
785 assert!(AssuranceLevel::TokenVerified < AssuranceLevel::Authenticated);
786 assert!(AssuranceLevel::Authenticated < AssuranceLevel::Sovereign);
787 }
788
789 #[test]
790 fn assurance_level_serde_roundtrip() {
791 let variants = [
792 AssuranceLevel::SelfAsserted,
793 AssuranceLevel::TokenVerified,
794 AssuranceLevel::Authenticated,
795 AssuranceLevel::Sovereign,
796 ];
797 for level in variants {
798 let json = serde_json::to_string(&level).unwrap();
799 let parsed: AssuranceLevel = serde_json::from_str(&json).unwrap();
800 assert_eq!(parsed, level);
801 }
802 }
803
804 #[test]
805 fn assurance_level_serde_snake_case() {
806 assert_eq!(
807 serde_json::to_string(&AssuranceLevel::SelfAsserted).unwrap(),
808 "\"self_asserted\""
809 );
810 assert_eq!(
811 serde_json::to_string(&AssuranceLevel::TokenVerified).unwrap(),
812 "\"token_verified\""
813 );
814 assert_eq!(
815 serde_json::to_string(&AssuranceLevel::Authenticated).unwrap(),
816 "\"authenticated\""
817 );
818 assert_eq!(
819 serde_json::to_string(&AssuranceLevel::Sovereign).unwrap(),
820 "\"sovereign\""
821 );
822 }
823
824 #[test]
825 fn assurance_level_from_str() {
826 assert_eq!(
827 "sovereign".parse::<AssuranceLevel>().unwrap(),
828 AssuranceLevel::Sovereign
829 );
830 assert_eq!(
831 "authenticated".parse::<AssuranceLevel>().unwrap(),
832 AssuranceLevel::Authenticated
833 );
834 assert_eq!(
835 "token_verified".parse::<AssuranceLevel>().unwrap(),
836 AssuranceLevel::TokenVerified
837 );
838 assert_eq!(
839 "self_asserted".parse::<AssuranceLevel>().unwrap(),
840 AssuranceLevel::SelfAsserted
841 );
842 assert!("invalid".parse::<AssuranceLevel>().is_err());
843 }
844
845 #[test]
846 fn assurance_level_from_str_case_insensitive() {
847 assert_eq!(
848 "SOVEREIGN".parse::<AssuranceLevel>().unwrap(),
849 AssuranceLevel::Sovereign
850 );
851 assert_eq!(
852 "Authenticated".parse::<AssuranceLevel>().unwrap(),
853 AssuranceLevel::Authenticated
854 );
855 }
856
857 #[test]
858 fn assurance_level_score() {
859 assert_eq!(AssuranceLevel::SelfAsserted.score(), 1);
860 assert_eq!(AssuranceLevel::TokenVerified.score(), 2);
861 assert_eq!(AssuranceLevel::Authenticated.score(), 3);
862 assert_eq!(AssuranceLevel::Sovereign.score(), 4);
863 }
864
865 #[test]
866 fn assurance_level_display() {
867 assert_eq!(AssuranceLevel::SelfAsserted.to_string(), "Self-Asserted");
868 assert_eq!(AssuranceLevel::TokenVerified.to_string(), "Token-Verified");
869 assert_eq!(AssuranceLevel::Authenticated.to_string(), "Authenticated");
870 assert_eq!(AssuranceLevel::Sovereign.to_string(), "Sovereign");
871 }
872}