use acdp_did::web::WebResolver;
use acdp_primitives::error::AcdpError;
use acdp_types::primitives::{ContentHash, CtxId, LineageId};
use acdp_types::receipt::{LineageHeadReceipt, RegistryReceipt};
pub async fn verify_receipt_value(
value: &serde_json::Value,
expected_ctx_id: &CtxId,
body: &acdp_types::body::Body,
recomputed_body_hash: &ContentHash,
producer_key_fingerprint: &str,
serving_authority: &str,
resolver: &WebResolver,
) -> Result<RegistryReceipt, AcdpError> {
let receipt = RegistryReceipt::from_value(value)?;
RegistryReceipt::validate_created_at_form(value)?;
let expected_did = acdp_did::web::authority_to_did_web(serving_authority);
if receipt.registry_did != expected_did {
return Err(AcdpError::InvalidReceipt(format!(
"receipt registry_did '{}' ≠ serving authority's DID '{expected_did}'",
receipt.registry_did
)));
}
receipt.cross_check(
expected_ctx_id,
recomputed_body_hash,
producer_key_fingerprint,
)?;
receipt.cross_check_body(body)?;
let key_id = &receipt.signature.key_id;
let (did_part, fragment) = key_id.split_once('#').ok_or_else(|| {
AcdpError::InvalidReceipt(format!(
"receipt signature.key_id '{key_id}' has no fragment"
))
})?;
if did_part != receipt.registry_did {
return Err(AcdpError::InvalidReceipt(format!(
"receipt signature.key_id DID '{did_part}' ≠ registry_did '{}'",
receipt.registry_did
)));
}
let doc = resolver.resolve(did_part).await?;
let method = doc.find_by_fragment(fragment).ok_or_else(|| {
AcdpError::InvalidReceipt(format!(
"registry DID document has no verification method '#{fragment}' — \
receipt keys (including retired ones) must remain in verificationMethod"
))
})?;
let raw_hash = RegistryReceipt::preimage_hash_of_value(value)?;
match receipt.signature.algorithm.as_str() {
"ed25519" => {
let key = method
.ed25519_public_key_bytes()
.map_err(|e| AcdpError::InvalidReceipt(format!("receipt key extraction: {e}")))?;
receipt.verify_signature_against_hash(&raw_hash, Some(&key), None)?;
}
"ecdsa-p256" => {
let key = method
.ecdsa_p256_public_key_sec1()
.map_err(|e| AcdpError::InvalidReceipt(format!("receipt key extraction: {e}")))?;
receipt.verify_signature_against_hash(&raw_hash, None, Some(&key))?;
}
other => {
return Err(AcdpError::InvalidReceipt(format!(
"receipt signature algorithm '{other}' is not supported"
)));
}
}
Ok(receipt)
}
#[allow(clippy::too_many_arguments)]
pub async fn verify_lineage_head_receipt_value(
value: &serde_json::Value,
requested_lineage: &LineageId,
served_ctx_id: &CtxId,
served_version: u32,
served_status: &acdp_types::primitives::Status,
on_current_endpoint: bool,
serving_authority: &str,
capabilities_registry_did: &str,
max_clock_skew: chrono::Duration,
resolver: &WebResolver,
) -> Result<LineageHeadReceipt, AcdpError> {
let receipt = LineageHeadReceipt::from_value(value)?;
receipt.cross_check_registry_binding(serving_authority, capabilities_registry_did)?;
receipt.cross_check_lineage(requested_lineage)?;
receipt.cross_check_head(
served_ctx_id,
served_version,
served_status,
on_current_endpoint,
)?;
let key_id = &receipt.signature.key_id;
let (did_part, fragment) = key_id.split_once('#').ok_or_else(|| {
AcdpError::InvalidReceipt(format!(
"lineage_head_receipt signature.key_id '{key_id}' has no fragment"
))
})?;
let doc = resolver.resolve(did_part).await?;
let method = doc.find_by_fragment(fragment).ok_or_else(|| {
AcdpError::InvalidReceipt(format!(
"registry DID document has no verification method '#{fragment}' — \
receipt keys (including retired ones) must remain in verificationMethod"
))
})?;
let raw_hash = LineageHeadReceipt::preimage_hash_of_value(value)?;
match receipt.signature.algorithm.as_str() {
"ed25519" => {
let key = method
.ed25519_public_key_bytes()
.map_err(|e| AcdpError::InvalidReceipt(format!("receipt key extraction: {e}")))?;
receipt.verify_signature_against_hash(&raw_hash, Some(&key), None)?;
}
"ecdsa-p256" => {
let key = method
.ecdsa_p256_public_key_sec1()
.map_err(|e| AcdpError::InvalidReceipt(format!("receipt key extraction: {e}")))?;
receipt.verify_signature_against_hash(&raw_hash, None, Some(&key))?;
}
other => {
return Err(AcdpError::InvalidReceipt(format!(
"receipt signature algorithm '{other}' is not supported"
)));
}
}
receipt.check_as_of_skew(chrono::Utc::now(), max_clock_skew)?;
Ok(receipt)
}