use std::sync::Arc;
use affinidi_crypto::KeyType;
#[allow(unused_imports)] use affinidi_data_integrity::VerificationMethodResolver;
use affinidi_data_integrity::{DataIntegrityError, ResolvedKey};
use affinidi_did_common::verification_method::{VerificationMethod, VerificationRelationship};
use affinidi_did_common::Document;
use affinidi_did_resolver_cache_sdk::DIDCacheClient;
use affinidi_encoding::{ED25519_PUB, P256_PUB, P384_PUB, SECP256K1_PUB, X25519_PUB};
use async_trait::async_trait;
use super::purpose::{split_vm, ProofPurpose, ProofPurposeResolver};
#[derive(Clone)]
pub struct CachedDidResolver {
client: Arc<DIDCacheClient>,
}
impl CachedDidResolver {
pub fn new(client: Arc<DIDCacheClient>) -> Self {
Self { client }
}
pub fn client(&self) -> &Arc<DIDCacheClient> {
&self.client
}
async fn resolve_for(
&self,
vm: &str,
purpose: ProofPurpose,
) -> Result<ResolvedKey, DataIntegrityError> {
let (did, _) = split_vm(vm)?;
let resolve = self.client.resolve(did).await.map_err(|e| {
DataIntegrityError::Resolver(format!("resolve the verificationMethod's DID: {e}"))
})?;
let method = authorised_method(&resolve.doc, did, vm, purpose)?;
let (codec, public_key_bytes) = method.decode_public_key().map_err(|e| {
DataIntegrityError::Resolver(format!("decode the verificationMethod's key: {e}"))
})?;
let key_type = codec_to_key_type(codec).ok_or_else(|| {
DataIntegrityError::Resolver(format!(
"unsupported multicodec 0x{codec:x} on the verificationMethod"
))
})?;
Ok(ResolvedKey::new(key_type, public_key_bytes))
}
}
#[async_trait]
impl ProofPurposeResolver for CachedDidResolver {
async fn resolve_vm_for_purpose(
&self,
vm: &str,
purpose: ProofPurpose,
) -> Result<ResolvedKey, DataIntegrityError> {
self.resolve_for(vm, purpose).await
}
}
fn relationship(doc: &Document, purpose: ProofPurpose) -> &[VerificationRelationship] {
match purpose {
ProofPurpose::AssertionMethod => &doc.assertion_method,
ProofPurpose::Authentication => &doc.authentication,
ProofPurpose::CapabilityInvocation => &doc.capability_invocation,
ProofPurpose::CapabilityDelegation => &doc.capability_delegation,
}
}
fn authorised_method(
doc: &Document,
did: &str,
vm: &str,
purpose: ProofPurpose,
) -> Result<VerificationMethod, DataIntegrityError> {
if doc.id.as_str() != did {
return Err(DataIntegrityError::Resolver(
"the resolved DID document's id is not the verificationMethod's DID".to_string(),
));
}
let names_vm = |id: &str| match id.strip_prefix('#') {
Some(fragment) => {
vm.strip_prefix(did).and_then(|rest| rest.strip_prefix('#')) == Some(fragment)
}
None => id == vm,
};
let embedded = |r: &VerificationRelationship| match r {
VerificationRelationship::VerificationMethod(m) if names_vm(m.id.as_str()) => {
Some((**m).clone())
}
_ => None,
};
let listed = relationship(doc, purpose).iter().find_map(|r| match r {
VerificationRelationship::Reference(id) if names_vm(id) => Some(None),
other => embedded(other).map(Some),
});
let method = match listed {
None => {
return Err(DataIntegrityError::Resolver(format!(
"verificationMethod is not listed under {purpose} in its DID document"
)));
}
Some(Some(method)) => method,
Some(None) => doc
.verification_method
.iter()
.find(|m| names_vm(m.id.as_str()))
.cloned()
.ok_or_else(|| {
DataIntegrityError::Resolver(
"verificationMethod is referenced but not defined under verificationMethod \
in its DID document"
.to_string(),
)
})?,
};
if method.controller.as_str() != did {
return Err(DataIntegrityError::Resolver(
"verificationMethod's controller is not the DID that names it".to_string(),
));
}
Ok(method)
}
fn codec_to_key_type(codec: u64) -> Option<KeyType> {
match codec {
c if c == ED25519_PUB => Some(KeyType::Ed25519),
c if c == X25519_PUB => Some(KeyType::X25519),
c if c == P256_PUB => Some(KeyType::P256),
c if c == P384_PUB => Some(KeyType::P384),
c if c == SECP256K1_PUB => Some(KeyType::Secp256k1),
_ => None,
}
}