use std::ops::ControlFlow;
use super::Event;
use super::compute_event_said;
use super::resolve::parse_did_keri;
use super::types::Prefix;
use crate::ports::registry::{RegistryBackend, RegistryError};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum KelResolveError {
#[error("'{0}' is not a did:keri identifier")]
InvalidDid(String),
#[error("KEL not found for {0}")]
NotFound(String),
#[error("resolved KEL is for a different identity: requested {requested}, derived {derived}")]
PrefixMismatch {
requested: String,
derived: String,
},
#[error("KEL processing failed: {0}")]
Replay(String),
#[error("could not reach the remote KEL source: {0}")]
Network(String),
#[error("resolved KEL exceeds the size bound: {0}")]
Oversized(String),
#[error("resolved KEL is truncated (inception missing)")]
Truncated,
#[error(
"remote KEL is older than the locally-trusted state (rollback): \
local tip seq {local_tip}, remote tip seq {remote_tip}"
)]
Rollback {
local_tip: u128,
remote_tip: u128,
},
#[error("KEL source error: {0}")]
Backend(String),
#[error("cross-source KEL fork at sequence {sequence}: conflicting event SAIDs {saids:?}")]
Diverging {
sequence: u128,
saids: Vec<String>,
},
}
pub trait KelResolver {
fn resolve_kel(&self, did: &str) -> Result<Vec<Event>, KelResolveError>;
}
pub fn collect_kel(
registry: &dyn RegistryBackend,
prefix: &Prefix,
) -> Result<Vec<Event>, KelResolveError> {
let mut events = Vec::new();
match registry.visit_events(prefix, 0, &mut |event| {
events.push(event.clone());
ControlFlow::Continue(())
}) {
Ok(()) => {}
Err(RegistryError::NotFound { .. }) => {
return Err(KelResolveError::NotFound(format!("did:keri:{prefix}")));
}
Err(e) => return Err(KelResolveError::Backend(e.to_string())),
}
if events.is_empty() {
return Err(KelResolveError::NotFound(format!("did:keri:{prefix}")));
}
Ok(events)
}
pub fn verify_prefix_binding(prefix: &Prefix, events: &[Event]) -> Result<(), KelResolveError> {
let Some(inception) = events.first() else {
return Err(KelResolveError::NotFound(format!("did:keri:{prefix}")));
};
let derived =
compute_event_said(inception).map_err(|e| KelResolveError::Replay(e.to_string()))?;
if derived.as_str() != prefix.as_str() {
return Err(KelResolveError::PrefixMismatch {
requested: prefix.as_str().to_string(),
derived: derived.as_str().to_string(),
});
}
Ok(())
}
pub struct LocalKelResolver<'a> {
registry: &'a dyn RegistryBackend,
}
impl<'a> LocalKelResolver<'a> {
pub fn new(registry: &'a dyn RegistryBackend) -> Self {
Self { registry }
}
}
impl KelResolver for LocalKelResolver<'_> {
fn resolve_kel(&self, did: &str) -> Result<Vec<Event>, KelResolveError> {
let prefix =
parse_did_keri(did).map_err(|_| KelResolveError::InvalidDid(did.to_string()))?;
let events = collect_kel(self.registry, &prefix)?;
verify_prefix_binding(&prefix, &events)?;
Ok(events)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::{KelResolveError, KelResolver, LocalKelResolver, verify_prefix_binding};
use auths_keri::{
CesrKey, Event, IcpEvent, IcpEventInit, KeriPublicKey, KeriSequence, Prefix, Said,
Threshold, VersionString, compute_next_commitment, finalize_icp_event,
};
fn cesr(pk: &KeriPublicKey) -> CesrKey {
CesrKey::new_unchecked(pk.to_qb64().expect("qb64"))
}
fn dummy_key(seed: u8) -> KeriPublicKey {
KeriPublicKey::ed25519(&[seed; 32]).expect("ed25519")
}
fn icp_event() -> IcpEvent {
finalize_icp_event(IcpEvent::new(IcpEventInit {
v: VersionString::placeholder(),
d: Said::default(),
i: Prefix::default(),
s: KeriSequence::new(0),
kt: Threshold::Simple(1),
k: vec![cesr(&dummy_key(1))],
nt: Threshold::Simple(1),
n: vec![compute_next_commitment(&dummy_key(2))],
bt: Threshold::Simple(0),
b: vec![],
c: vec![],
a: vec![],
}))
.expect("icp")
}
#[test]
fn prefix_binding_accepts_matching_prefix() {
let icp = icp_event();
let prefix = icp.i.clone();
let events = vec![Event::Icp(icp)];
assert!(verify_prefix_binding(&prefix, &events).is_ok());
}
#[test]
fn prefix_binding_rejects_substituted_kel() {
let icp = icp_event();
let wrong =
Prefix::new_unchecked("ENotTheRightPrefixAtAll0000000000000000000000".to_string());
let events = vec![Event::Icp(icp)];
let err = verify_prefix_binding(&wrong, &events).unwrap_err();
assert!(matches!(err, KelResolveError::PrefixMismatch { .. }));
}
#[test]
fn prefix_binding_rejects_empty_kel() {
let prefix =
Prefix::new_unchecked("EwhateverPrefix000000000000000000000000000000".to_string());
let err = verify_prefix_binding(&prefix, &[]).unwrap_err();
assert!(matches!(err, KelResolveError::NotFound(_)));
}
#[cfg(feature = "test-utils")]
#[test]
fn local_resolver_rejects_non_keri_did() {
let registry = crate::testing::fakes::FakeRegistryBackend::new();
let resolver = LocalKelResolver::new(®istry);
let err = resolver
.resolve_kel("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK")
.unwrap_err();
assert!(matches!(err, KelResolveError::InvalidDid(_)));
}
#[cfg(feature = "test-utils")]
#[test]
fn local_resolver_returns_known_kel() {
use crate::ports::registry::RegistryBackend;
let icp = icp_event();
let prefix = icp.i.clone();
let did = format!("did:keri:{prefix}");
let event = Event::Icp(icp);
let registry = crate::testing::fakes::FakeRegistryBackend::new();
registry.append_event(&prefix, &event).expect("append");
let resolver = LocalKelResolver::new(®istry);
let events = resolver.resolve_kel(&did).expect("resolve");
assert_eq!(events, vec![event]);
}
#[cfg(feature = "test-utils")]
#[test]
fn local_resolver_unknown_identity_is_not_found() {
let registry = crate::testing::fakes::FakeRegistryBackend::new();
let resolver = LocalKelResolver::new(®istry);
let err = resolver
.resolve_kel("did:keri:ENeverWasInTheRegistry00000000000000000000000")
.unwrap_err();
assert!(matches!(err, KelResolveError::NotFound(_)));
}
}