use affinidi_did_resolver_cache_sdk::DIDCacheClient;
use serde_json::Value;
use vti_common::error::AppError;
pub(crate) fn credential_issuer(credential: &Value) -> Option<String> {
let issuer = credential.get("issuer")?;
issuer
.as_str()
.map(str::to_string)
.or_else(|| issuer.get("id").and_then(Value::as_str).map(str::to_string))
}
pub(crate) async fn resolve_di_issuer_key(
did_resolver: Option<&DIDCacheClient>,
credential: &Value,
) -> Result<Vec<u8>, AppError> {
let issuer_did = credential_issuer(credential)
.ok_or_else(|| AppError::Validation("Data-Integrity credential has no `issuer`".into()))?;
let vm = credential
.get("proof")
.and_then(|p| p.get("verificationMethod"))
.and_then(Value::as_str)
.ok_or_else(|| {
AppError::Validation("Data-Integrity proof has no `verificationMethod`".into())
})?;
let vm_base = vm.split('#').next().unwrap_or_default();
if vm_base != issuer_did {
return Err(AppError::Validation(format!(
"DI proof verificationMethod `{vm}` is not under the credential issuer \
`{issuer_did}` — refusing a credential signed by a key outside the issuer DID"
)));
}
if issuer_did.starts_with("did:key:") {
return affinidi_crypto::did_key::did_key_to_ed25519_pub(&issuer_did)
.map(|k| k.to_vec())
.map_err(|e| {
AppError::Validation(format!(
"issuer `{issuer_did}` is not a resolvable did:key: {e}"
))
});
}
let resolver = did_resolver.ok_or_else(|| {
AppError::Validation(format!(
"resolving issuer `{issuer_did}` needs a DID resolver, but none is configured — \
configure the DID cache client to receive Data-Integrity credentials from \
did:webvh / did:web issuers"
))
})?;
resolve_vm_ed25519(resolver, &issuer_did, vm).await
}
async fn resolve_vm_ed25519(
resolver: &DIDCacheClient,
did: &str,
vm: &str,
) -> Result<Vec<u8>, AppError> {
let resolved = resolver
.resolve(did)
.await
.map_err(|e| AppError::Validation(format!("issuer DID `{did}` did not resolve: {e}")))?;
let doc: Value = serde_json::to_value(&resolved.doc)
.map_err(|e| AppError::Internal(format!("issuer DID document serialise failed: {e}")))?;
let vms = doc
.get("verificationMethod")
.and_then(Value::as_array)
.ok_or_else(|| {
AppError::Validation(format!(
"issuer DID `{did}` has no verificationMethod array"
))
})?;
let relative = vm
.split_once('#')
.map(|(_, frag)| format!("#{frag}"))
.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 issuer DID `{did}`"
))
})?;
let multibase = entry
.get("publicKeyMultibase")
.and_then(Value::as_str)
.ok_or_else(|| {
AppError::Validation(format!(
"verificationMethod `{vm}` has no publicKeyMultibase (only Multikey-encoded \
Ed25519 VMs are supported)"
))
})?;
affinidi_crypto::did_key::did_key_to_ed25519_pub(&format!("did:key:{multibase}"))
.map(|k| k.to_vec())
.map_err(|e| {
AppError::Validation(format!(
"verificationMethod `{vm}` is not an Ed25519 Multikey: {e}"
))
})
}