auths_id/identity/
resolve.rs1use auths_keri::KeriPublicKey;
4use git2::Repository;
5use std::path::Path;
6
7use std::sync::Arc;
8
9use crate::keri::types::Prefix;
10use crate::keri::{DidKeriResolution, resolve_did_keri};
11use crate::storage::registry::RegistryBackend;
12
13pub use auths_core::signing::{DidResolver, DidResolverError, ResolvedDid};
14
15pub struct DefaultDidResolver {
17 repo_path: Option<std::path::PathBuf>,
18}
19
20impl DefaultDidResolver {
21 pub fn new() -> Self {
23 Self { repo_path: None }
24 }
25
26 pub fn with_repo(path: impl AsRef<Path>) -> Self {
28 Self {
29 repo_path: Some(path.as_ref().to_path_buf()),
30 }
31 }
32
33 fn resolve_did_key(&self, did: &str) -> Result<ResolvedDid, DidResolverError> {
34 let decoded = auths_crypto::did_key_decode(did)
36 .map_err(|e| DidResolverError::DidKeyDecodingFailed(e.to_string()))?;
37 let public_key_bytes = match decoded {
38 auths_crypto::DecodedDidKey::Ed25519(pk) => pk.to_vec(),
39 auths_crypto::DecodedDidKey::P256(pk) => pk,
40 };
41 Ok(ResolvedDid::Key {
42 did: did.to_string(),
43 public_key_bytes,
44 })
45 }
46
47 fn resolve_did_keri_internal(&self, did: &str) -> Result<ResolvedDid, DidResolverError> {
48 let repo_path = self.repo_path.as_ref().ok_or_else(|| {
49 DidResolverError::Repository("Repository path required for did:keri".into())
50 })?;
51
52 let repo =
53 Repository::open(repo_path).map_err(|e| DidResolverError::Repository(e.to_string()))?;
54
55 let resolution: DidKeriResolution = resolve_did_keri(&repo, did)
56 .map_err(|e| DidResolverError::Resolution(e.to_string()))?;
57
58 let curve = resolution.curve;
59 Ok(ResolvedDid::Keri {
60 did: did.to_string(),
61 public_key_bytes: resolution.public_key,
62 curve,
63 sequence: resolution.sequence,
64 can_rotate: resolution.can_rotate,
65 })
66 }
67}
68
69impl Default for DefaultDidResolver {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl DidResolver for DefaultDidResolver {
76 fn resolve(&self, did: &str) -> Result<ResolvedDid, DidResolverError> {
77 if did.starts_with("did:keri:") {
78 self.resolve_did_keri_internal(did)
79 } else if did.starts_with("did:key:") {
80 self.resolve_did_key(did)
81 } else {
82 let method = did.split(':').nth(1).unwrap_or("unknown");
83 Err(DidResolverError::UnsupportedMethod(method.to_string()))
84 }
85 }
86}
87
88pub struct RegistryDidResolver {
95 backend: Arc<dyn RegistryBackend + Send + Sync>,
96}
97
98impl RegistryDidResolver {
99 pub fn new(backend: Arc<dyn RegistryBackend + Send + Sync>) -> Self {
109 Self { backend }
110 }
111}
112
113impl DidResolver for RegistryDidResolver {
114 fn resolve(&self, did: &str) -> Result<ResolvedDid, DidResolverError> {
115 if did.starts_with("did:keri:") {
116 let Some(prefix) = did.strip_prefix("did:keri:") else {
117 unreachable!("starts_with guard ensures strip_prefix succeeds");
118 };
119 if prefix.is_empty() {
120 return Err(DidResolverError::InvalidDidKeyFormat(
121 "Empty KERI prefix".into(),
122 ));
123 }
124 let key_state = self
125 .backend
126 .get_key_state(&Prefix::new_unchecked(prefix.to_string()))
127 .map_err(|e| DidResolverError::Repository(e.to_string()))?;
128 let key_encoded = key_state.current_key().ok_or_else(|| {
129 DidResolverError::Repository("No current key in key state".into())
130 })?;
131 let parsed = KeriPublicKey::parse(key_encoded.as_str())
132 .map_err(|e| DidResolverError::DidKeyDecodingFailed(e.to_string()))?;
133 let curve = parsed.curve();
134 Ok(ResolvedDid::Keri {
135 did: did.to_string(),
136 public_key_bytes: parsed.into_bytes(),
137 curve,
138 sequence: key_state.sequence,
139 can_rotate: key_state.can_rotate(),
140 })
141 } else if did.starts_with("did:key:") {
142 let decoded = auths_crypto::did_key_decode(did)
143 .map_err(|e| DidResolverError::DidKeyDecodingFailed(e.to_string()))?;
144 let public_key_bytes = match decoded {
145 auths_crypto::DecodedDidKey::Ed25519(pk) => pk.to_vec(),
146 auths_crypto::DecodedDidKey::P256(pk) => pk,
147 };
148 Ok(ResolvedDid::Key {
149 did: did.to_string(),
150 public_key_bytes,
151 })
152 } else {
153 let method = did.split(':').nth(1).unwrap_or("unknown");
154 Err(DidResolverError::UnsupportedMethod(method.to_string()))
155 }
156 }
157}
158
159#[cfg(test)]
165#[allow(clippy::disallowed_methods)]
166mod tests {
167 use super::*;
168 use crate::keri::create_keri_identity_with_curve;
169 use tempfile::TempDir;
170
171 fn setup_repo() -> (TempDir, Repository) {
172 let dir = TempDir::new().unwrap();
173 let repo = Repository::init(dir.path()).unwrap();
174
175 let mut config = repo.config().unwrap();
176 config.set_str("user.name", "Test User").unwrap();
177 config.set_str("user.email", "test@example.com").unwrap();
178
179 (dir, repo)
180 }
181
182 #[test]
183 fn did_key_roundtrip() {
184 let key_bytes = [42u8; 32];
185 let did = auths_verifier::types::CanonicalDid::from_public_key_did_key(
186 &key_bytes,
187 auths_crypto::CurveType::Ed25519,
188 );
189 let decoded = auths_crypto::did_key_decode(did.as_str()).unwrap();
190 match decoded {
191 auths_crypto::DecodedDidKey::Ed25519(pk) => assert_eq!(pk, key_bytes),
192 _ => panic!("expected Ed25519"),
193 }
194 }
195
196 #[test]
197 fn resolves_did_key_without_repo() {
198 let resolver = DefaultDidResolver::new();
199
200 let key = [1u8; 32];
201 let did = auths_verifier::CanonicalDid::from_public_key_did_key(
202 &key,
203 auths_crypto::CurveType::Ed25519,
204 )
205 .to_string();
206
207 let resolved = resolver.resolve(&did).unwrap();
208 assert_eq!(resolved.public_key_bytes(), &key);
209 assert!(resolved.is_key());
210 }
211
212 #[test]
213 fn resolves_did_keri_with_repo() {
214 let (dir, repo) = setup_repo();
215
216 let init = create_keri_identity_with_curve(
217 &repo,
218 None,
219 chrono::Utc::now(),
220 auths_crypto::CurveType::Ed25519,
221 )
222 .unwrap();
223 let did = format!("did:keri:{}", init.prefix);
224
225 let resolver = DefaultDidResolver::with_repo(dir.path());
226 let resolved = resolver.resolve(&did).unwrap();
227
228 assert_eq!(
229 resolved.public_key_bytes(),
230 init.current_public_key.as_slice()
231 );
232 assert!(matches!(
233 resolved,
234 ResolvedDid::Keri {
235 sequence: 0,
236 can_rotate: true,
237 ..
238 }
239 ));
240 }
241
242 #[test]
243 fn did_keri_fails_without_repo() {
244 let resolver = DefaultDidResolver::new();
245 let result = resolver.resolve("did:keri:ETest123");
246 assert!(matches!(result, Err(DidResolverError::Repository(_))));
247 }
248
249 #[test]
250 fn rejects_unsupported_method() {
251 let resolver = DefaultDidResolver::new();
252 let result = resolver.resolve("did:web:example.com");
253 assert!(matches!(
254 result,
255 Err(DidResolverError::UnsupportedMethod(_))
256 ));
257 }
258
259 #[test]
260 fn rejects_invalid_did_key() {
261 let result = auths_crypto::did_key_decode("did:key:invalid");
262 assert!(result.is_err());
263 }
264
265 #[test]
266 fn rejects_non_did_key_prefix() {
267 let result = auths_crypto::did_key_decode("did:web:example.com");
268 assert!(result.is_err());
269 }
270}