1use auths_core::trust::continuity::{KelContinuityChecker, RotationProof};
7use auths_crypto::KeriPublicKey;
8use git2::Repository;
9
10use crate::keri::{Event, GitKel, Said, did_to_prefix, validate_kel};
11
12pub struct GitKelContinuityChecker<'a> {
35 repo: &'a Repository,
36}
37
38impl<'a> GitKelContinuityChecker<'a> {
39 pub fn new(repo: &'a Repository) -> Self {
41 Self { repo }
42 }
43}
44
45impl KelContinuityChecker for GitKelContinuityChecker<'_> {
46 fn verify_rotation_continuity(
47 &self,
48 did: &str,
49 pinned_tip_said: &str,
50 presented_pk: &[u8],
51 ) -> Result<Option<RotationProof>, auths_core::error::TrustError> {
52 let pinned_said = Said::new_unchecked(pinned_tip_said.to_string());
53 let prefix = did_to_prefix(did).ok_or_else(|| {
54 auths_core::error::TrustError::InvalidData(format!("Invalid did:keri format: {}", did))
55 })?;
56
57 let kel = GitKel::new(self.repo, prefix);
58 if !kel.exists() {
59 return Ok(None);
60 }
61
62 let events = kel
63 .get_events()
64 .map_err(|e| auths_core::error::TrustError::InvalidData(e.to_string()))?;
65
66 let pinned_idx = events.iter().position(|e| e.said() == pinned_tip_said);
67 let Some(pinned_idx) = pinned_idx else {
68 return Ok(None);
69 };
70
71 let full_state = validate_kel(&events)
72 .map_err(|e| auths_core::error::TrustError::InvalidData(e.to_string()))?;
73
74 if !verify_chain_from_index(&events, pinned_idx, &pinned_said) {
75 return Ok(None);
76 }
77
78 let current_key_encoded = match full_state.current_key() {
79 Some(k) => k,
80 None => return Ok(None),
81 };
82 let current_key_bytes = KeriPublicKey::parse(current_key_encoded).map_err(|e| {
83 auths_core::error::TrustError::InvalidData(format!("KERI key decode failed: {e}"))
84 })?;
85
86 if current_key_bytes.as_bytes().as_slice() != presented_pk {
87 return Ok(None);
88 }
89
90 Ok(Some(RotationProof {
91 new_public_key: current_key_bytes.as_bytes().to_vec(),
92 new_kel_tip: full_state.last_event_said.to_string(),
93 new_sequence: full_state.sequence,
94 }))
95 }
96}
97
98fn verify_chain_from_index(events: &[Event], pinned_idx: usize, pinned_tip_said: &Said) -> bool {
103 let mut expected_prev = pinned_tip_said.clone();
104
105 for event in events.iter().skip(pinned_idx + 1) {
106 let prev = match event.previous() {
107 Some(p) => p,
108 None => return false, };
110 if *prev != expected_prev {
111 return false; }
113 expected_prev = event.said().clone();
114 }
115
116 true
117}
118
119#[cfg(test)]
120mod tests {
121 use auths_crypto::KeriPublicKey;
122
123 #[test]
124 fn test_keri_key_parse_valid() {
125 let encoded = "DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
126 let key = KeriPublicKey::parse(encoded).unwrap();
127 assert_eq!(key.as_bytes(), &[0u8; 32]);
128 }
129
130 #[test]
131 fn test_keri_key_parse_invalid_prefix() {
132 let result = KeriPublicKey::parse("XInvalidPrefix");
133 assert!(result.is_err());
134 }
135
136 #[test]
137 fn test_keri_key_parse_invalid_base64() {
138 let result = KeriPublicKey::parse("D!!!invalid!!!");
139 assert!(result.is_err());
140 }
141}