use serde_json::Value;
use vta_sdk::trust_task_proof::{
ProofPurpose, PurposeVmResolver, proof_set, proof_signer_did, verify_proof_set,
};
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 async fn verify_di_issuer_proofs(
resolver: &(dyn PurposeVmResolver + '_),
credential: &Value,
) -> Result<String, AppError> {
let issuer_did = credential_issuer(credential)
.ok_or_else(|| AppError::Validation("Data-Integrity credential has no `issuer`".into()))?;
let proof_value = credential
.get("proof")
.ok_or_else(|| AppError::Validation("Data-Integrity credential has no `proof`".into()))?;
let is_bbs = |p: &Value| p.get("cryptosuite").and_then(Value::as_str) == Some("bbs-2023");
let bbs = match proof_value {
Value::Array(items) => items.iter().any(is_bbs),
single => is_bbs(single),
};
if bbs {
return Err(AppError::Validation(
"a bbs-2023 proof is not verified on the eddsa / ML-DSA Data-Integrity path \
(BBS+ is audit-gated and routed separately)"
.into(),
));
}
let raw: Vec<&Value> = match proof_value {
Value::Array(items) => items.iter().collect(),
single => vec![single],
};
if let Some(foreign) = raw
.iter()
.filter_map(|p| p.get("verificationMethod").and_then(Value::as_str))
.find(|vm| vm.split('#').next().unwrap_or_default() != issuer_did)
{
return Err(AppError::Validation(format!(
"DI proof verificationMethod `{foreign}` is not under the credential issuer \
`{issuer_did}` — refusing a credential signed by a key outside the issuer DID"
)));
}
let proofs = proof_set(proof_value)
.map_err(|e| AppError::Validation(format!("unreadable Data-Integrity proof: {e}")))?;
if proofs.iter().any(|p| proof_signer_did(p) != issuer_did) {
return Err(AppError::Validation(
"DI proof is not under the credential issuer".into(),
));
}
let verified = verify_proof_set(credential, ProofPurpose::AssertionMethod, resolver)
.await
.map_err(|e| {
AppError::Validation(format!(
"issuer Data-Integrity proof verification failed: {e}"
))
})?;
if verified.signer() != issuer_did {
return Err(AppError::Validation(
"DI proofs are not signed by the credential issuer".into(),
));
}
Ok(issuer_did)
}