use auths_core::storage::keychain::KeyAlias;
use auths_id::keri::Event;
use auths_id::keri::types::Prefix;
use auths_keri::witness::StoredReceipt;
use auths_keri::{Said, TelEvent};
use crate::context::AuthsContext;
use crate::domains::credentials::error::CredentialError;
use crate::domains::credentials::stored::StoredCredential;
use crate::domains::credentials::verify::{
ResolvedAsOf, collect_lifecycle_receipts, resolve_kel, resolve_tel, tip_as_of,
};
#[derive(Debug, Clone)]
pub struct PresentationInputs {
pub signed: auths_verifier::SignedAcdc,
pub issuer_kel: Vec<Event>,
pub tel: Vec<TelEvent>,
pub receipts: Vec<StoredReceipt>,
pub subject_kel: Vec<Event>,
pub subject_delegator_kel: Vec<Event>,
pub as_of: ResolvedAsOf,
}
pub fn load_presentation_inputs(
ctx: &AuthsContext,
issuer_alias: &KeyAlias,
credential_said: &str,
) -> Result<PresentationInputs, CredentialError> {
let issuer_prefix =
crate::domains::credentials::issue::resolve_issuer_prefix(ctx, issuer_alias)?;
let cred = Said::new_unchecked(credential_said.to_string());
let blob = ctx
.registry
.load_credential(&issuer_prefix, &cred)
.map_err(|e| CredentialError::StaleOrUnresolvable {
reason: format!("credential blob read failed: {e}"),
})?
.ok_or_else(|| CredentialError::StaleOrUnresolvable {
reason: format!("credential not found: {credential_said}"),
})?;
let stored =
StoredCredential::from_bytes(&blob).map_err(|e| CredentialError::StaleOrUnresolvable {
reason: format!("credential blob parse failed: {e}"),
})?;
let issuer_kel = resolve_kel(ctx, &issuer_prefix)?;
let tel = resolve_tel(ctx, &issuer_prefix, &stored.acdc.ri, &stored.acdc.d)?;
let receipts = collect_lifecycle_receipts(ctx, &issuer_prefix, &issuer_kel, &tel);
let as_of = tip_as_of(&issuer_kel);
let subject_prefix = Prefix::new_unchecked(stored.acdc.a.i.to_string());
let subject_kel = resolve_kel(ctx, &subject_prefix)?;
let subject_delegator_kel = match subject_delegator_prefix(&subject_kel) {
Some(delegator) => resolve_kel(ctx, &delegator)?,
None => Vec::new(),
};
let signed = auths_verifier::SignedAcdc {
acdc: stored.acdc.clone(),
signature: stored.signature.clone(),
};
Ok(PresentationInputs {
signed,
issuer_kel,
tel,
receipts,
subject_kel,
subject_delegator_kel,
as_of,
})
}
#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PresentationEvidence {
pub credential: EvidenceCredential,
pub issuer_kel: Vec<Event>,
pub issuer_kel_attachments_b64: Vec<String>,
pub subject_kel: Vec<Event>,
pub subject_kel_attachments_b64: Vec<String>,
pub delegator_kel: Vec<Event>,
pub delegator_kel_attachments_b64: Vec<String>,
pub tel: Vec<TelEvent>,
}
#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EvidenceCredential {
pub acdc: auths_keri::Acdc,
pub signature_b64: String,
}
pub fn load_presentation_evidence(
ctx: &AuthsContext,
issuer_alias: &KeyAlias,
credential_said: &str,
) -> Result<PresentationEvidence, CredentialError> {
use base64::Engine as _;
let b64 = base64::engine::general_purpose::STANDARD;
let inputs = load_presentation_inputs(ctx, issuer_alias, credential_said)?;
let issuer_prefix =
crate::domains::credentials::issue::resolve_issuer_prefix(ctx, issuer_alias)?;
let subject_prefix = Prefix::new_unchecked(inputs.signed.acdc.a.i.to_string());
let delegator_prefix = subject_delegator_prefix(&inputs.subject_kel);
let attachments = |prefix: &Prefix, kel: &[Event]| -> Result<Vec<String>, CredentialError> {
kel.iter()
.map(|event| {
let seq = event.sequence().value();
ctx.registry
.get_attachment(prefix, seq)
.map_err(|e| CredentialError::StaleOrUnresolvable {
reason: format!("attachment read failed for {prefix} seq {seq}: {e}"),
})?
.map(|bytes| b64.encode(bytes))
.ok_or_else(|| CredentialError::StaleOrUnresolvable {
reason: format!(
"no signature attachment stored for {prefix} seq {seq} — this KEL cannot be exported as authenticatable evidence"
),
})
})
.collect()
};
let issuer_kel_attachments_b64 = attachments(&issuer_prefix, &inputs.issuer_kel)?;
let subject_kel_attachments_b64 = attachments(&subject_prefix, &inputs.subject_kel)?;
let delegator_kel_attachments_b64 = match &delegator_prefix {
Some(delegator) => attachments(delegator, &inputs.subject_delegator_kel)?,
None => Vec::new(),
};
Ok(PresentationEvidence {
credential: EvidenceCredential {
acdc: inputs.signed.acdc.clone(),
signature_b64: b64.encode(&inputs.signed.signature),
},
issuer_kel: inputs.issuer_kel,
issuer_kel_attachments_b64,
subject_kel: inputs.subject_kel,
subject_kel_attachments_b64,
delegator_kel: inputs.subject_delegator_kel,
delegator_kel_attachments_b64,
tel: inputs.tel,
})
}
fn subject_delegator_prefix(subject_kel: &[Event]) -> Option<Prefix> {
subject_kel
.iter()
.find_map(|event| event.delegator().cloned())
}