use affinidi_did_resolver_cache_sdk::DIDCacheClient;
use ed25519_dalek::VerifyingKey;
use vti_common::error::AppError;
pub(crate) struct DidVmResolver {
resolver: Option<DIDCacheClient>,
}
impl DidVmResolver {
pub(crate) fn new(resolver: Option<DIDCacheClient>) -> Self {
Self { resolver }
}
pub(crate) async fn resolve_ed25519(&self, vm: &str) -> Result<Vec<u8>, AppError> {
let base_did = vm.split('#').next().unwrap_or(vm);
if base_did.starts_with("did:key:") {
return affinidi_crypto::did_key::did_key_to_ed25519_pub(base_did)
.map(|k| k.to_vec())
.map_err(|e| {
AppError::Validation(format!("`{base_did}` is not a resolvable did:key: {e}"))
});
}
let resolver = self.resolver.as_ref().ok_or_else(|| {
AppError::Validation(format!(
"resolving `{base_did}` needs a DID resolver, but none is configured — configure \
the DID cache to verify did:webvh / did:web issuers + holders"
))
})?;
let resolved = resolver
.resolve(base_did)
.await
.map_err(|e| AppError::Validation(format!("DID `{base_did}` did not resolve: {e}")))?;
let relative = vm
.split_once('#')
.map(|(_, f)| format!("#{f}"))
.unwrap_or_default();
let entry = resolved
.doc
.verification_method
.iter()
.find(|m| m.id.as_str() == vm || m.id.as_str() == relative)
.ok_or_else(|| {
AppError::Validation(format!(
"verificationMethod `{vm}` not found in DID `{base_did}`"
))
})?;
entry.get_public_key_bytes().map_err(|e| {
AppError::Validation(format!(
"verificationMethod `{vm}` public key could not be extracted: {e}"
))
})
}
pub(crate) async fn resolve_verifying_key(&self, vm: &str) -> Result<VerifyingKey, AppError> {
let bytes = self.resolve_ed25519(vm).await?;
let arr: [u8; 32] = bytes.as_slice().try_into().map_err(|_| {
AppError::Validation(format!("verificationMethod `{vm}` key is not 32 bytes"))
})?;
VerifyingKey::from_bytes(&arr).map_err(|e| {
AppError::Validation(format!(
"verificationMethod `{vm}` is not a valid Ed25519 key: {e}"
))
})
}
#[cfg(feature = "bbs")]
pub(crate) async fn resolve_bbs_g2(&self, vm: &str) -> Result<[u8; 96], AppError> {
use serde_json::Value;
let base_did = vm.split('#').next().unwrap_or(vm);
if base_did.starts_with("did:key:") {
return affinidi_crypto::bls12381::did_key_to_g2_pub(base_did).map_err(|e| {
AppError::Validation(format!("`{base_did}` is not a BBS did:key: {e}"))
});
}
let resolver = self.resolver.as_ref().ok_or_else(|| {
AppError::Validation(format!(
"resolving `{base_did}` needs a DID resolver to verify did:webvh / did:web \
BBS issuers"
))
})?;
let resolved = resolver
.resolve(base_did)
.await
.map_err(|e| AppError::Validation(format!("DID `{base_did}` did not resolve: {e}")))?;
let doc: Value = serde_json::to_value(&resolved.doc)
.map_err(|e| AppError::Internal(format!("DID document serialise failed: {e}")))?;
let vms = doc
.get("verificationMethod")
.and_then(Value::as_array)
.ok_or_else(|| {
AppError::Validation(format!("DID `{base_did}` has no verificationMethod array"))
})?;
let relative = vm
.split_once('#')
.map(|(_, f)| format!("#{f}"))
.unwrap_or_default();
let entry = vms
.iter()
.find(|e| {
let id = e.get("id").and_then(Value::as_str).unwrap_or("");
id == vm || id == relative
})
.ok_or_else(|| {
AppError::Validation(format!(
"verificationMethod `{vm}` not found in DID `{base_did}`"
))
})?;
let multibase = entry
.get("publicKeyMultibase")
.and_then(Value::as_str)
.ok_or_else(|| {
AppError::Validation(format!(
"verificationMethod `{vm}` has no publicKeyMultibase (BLS12-381 G2 Multikey)"
))
})?;
affinidi_crypto::bls12381::did_key_to_g2_pub(&format!("did:key:{multibase}")).map_err(|e| {
AppError::Validation(format!(
"verificationMethod `{vm}` is not a BLS12-381 G2 Multikey: {e}"
))
})
}
}
#[async_trait::async_trait]
impl affinidi_data_integrity::VerificationMethodResolver for DidVmResolver {
async fn resolve_vm(
&self,
vm: &str,
) -> Result<affinidi_data_integrity::ResolvedKey, affinidi_data_integrity::DataIntegrityError>
{
let bytes = self
.resolve_ed25519(vm)
.await
.map_err(|e| affinidi_data_integrity::DataIntegrityError::Resolver(e.to_string()))?;
Ok(affinidi_data_integrity::ResolvedKey::new(
affinidi_secrets_resolver::secrets::KeyType::Ed25519,
bytes,
))
}
}
pub(crate) fn check_issuer_binding(vm: &str, issuer_did: &str) -> Result<(), AppError> {
let base = vm.split('#').next().unwrap_or(vm);
if base != issuer_did {
return Err(AppError::Validation(format!(
"proof verificationMethod `{vm}` is not under the issuer `{issuer_did}`"
)));
}
Ok(())
}