use auths_id::keri::{Event, KelResolveError, KelResolver, LocalKelResolver};
use auths_id::ports::registry::RegistryBackend;
pub struct KelResolverChain<'a> {
registry: &'a dyn RegistryBackend,
}
impl<'a> KelResolverChain<'a> {
pub fn local(registry: &'a dyn RegistryBackend) -> Self {
Self { registry }
}
pub fn resolve_kel(&self, did: &str) -> Result<Vec<Event>, KelResolveError> {
LocalKelResolver::new(self.registry).resolve_kel(did)
}
}
#[derive(Debug, thiserror::Error)]
pub enum CurrentKeyError {
#[error("KEL for {did} could not be resolved: {source}")]
Resolve {
did: String,
source: KelResolveError,
},
#[error("KEL for {did} failed validation: {reason}")]
InvalidKel {
did: String,
reason: String,
},
#[error("KEL for {did} has no current signing key")]
NoCurrentKey {
did: String,
},
#[error("current key for {did} could not be decoded: {reason}")]
UnsupportedKey {
did: String,
reason: String,
},
}
pub fn resolve_current_public_key(
registry: &dyn RegistryBackend,
did: &str,
) -> Result<(Vec<u8>, auths_crypto::CurveType), CurrentKeyError> {
let kel = KelResolverChain::local(registry)
.resolve_kel(did)
.map_err(|source| CurrentKeyError::Resolve {
did: did.to_string(),
source,
})?;
let delegator_kel = match kel.first() {
Some(Event::Dip(dip)) => Some(
KelResolverChain::local(registry)
.resolve_kel(&format!("did:keri:{}", dip.di))
.map_err(|source| CurrentKeyError::Resolve {
did: did.to_string(),
source,
})?,
),
_ => None,
};
let lookup = delegator_kel
.as_deref()
.map(auths_keri::KelSealIndex::from_events);
let state = auths_keri::TrustedKel::from_trusted_source(&kel)
.replay_with_lookup(
lookup
.as_ref()
.map(|l| l as &dyn auths_keri::DelegatorKelLookup),
)
.map_err(|e| CurrentKeyError::InvalidKel {
did: did.to_string(),
reason: e.to_string(),
})?;
let key = state
.current_key()
.ok_or_else(|| CurrentKeyError::NoCurrentKey {
did: did.to_string(),
})?;
let parsed = key.parse().map_err(|e| CurrentKeyError::UnsupportedKey {
did: did.to_string(),
reason: e.to_string(),
})?;
let (bytes, curve) = match parsed {
auths_keri::KeriPublicKey::Ed25519 { key: pk, .. } => {
(pk.to_vec(), auths_crypto::CurveType::Ed25519)
}
auths_keri::KeriPublicKey::P256 { key, .. } => {
(key.to_vec(), auths_crypto::CurveType::P256)
}
};
Ok((bytes, curve))
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use auths_id::testing::fakes::FakeRegistryBackend;
use auths_keri::{
CesrKey, IcpEvent, KeriPublicKey, KeriSequence, Prefix, Said, Threshold, VersionString,
compute_next_commitment, finalize_icp_event,
};
fn icp_and_prefix(seed: u8) -> (Event, Prefix) {
let key = KeriPublicKey::ed25519(&[seed; 32]).unwrap();
let next = KeriPublicKey::ed25519(&[seed.wrapping_add(1); 32]).unwrap();
let icp = IcpEvent {
v: VersionString::placeholder(),
d: Said::default(),
i: Prefix::default(),
s: KeriSequence::new(0),
kt: Threshold::Simple(1),
k: vec![CesrKey::new_unchecked(key.to_qb64().unwrap())],
nt: Threshold::Simple(1),
n: vec![compute_next_commitment(&next)],
bt: Threshold::Simple(0),
b: vec![],
c: vec![],
a: vec![],
};
let finalized = finalize_icp_event(icp).unwrap();
let prefix = finalized.i.clone();
(Event::Icp(finalized), prefix)
}
#[test]
fn local_only_resolves_from_local() {
let (event, prefix) = icp_and_prefix(1);
let did = format!("did:keri:{prefix}");
let registry = FakeRegistryBackend::new();
registry.append_event(&prefix, &event).unwrap();
let chain = KelResolverChain::local(®istry);
assert_eq!(chain.resolve_kel(&did).unwrap(), vec![event]);
}
#[test]
fn local_only_unknown_is_not_found() {
let registry = FakeRegistryBackend::new();
let chain = KelResolverChain::local(®istry);
let err = chain
.resolve_kel("did:keri:ENotHere0000000000000000000000000000000000")
.unwrap_err();
assert!(matches!(err, KelResolveError::NotFound(_)));
}
}