use auths_id::keri::types::Prefix;
use auths_id::keri::{Event, Seal, verify_event_said};
use auths_verifier::duplicity::{KelEventRef, detect_duplicity};
use auths_verifier::types::IdentityDID;
use serde::Serialize;
use crate::domains::org::audit::AuthorityAtSigning;
use crate::domains::org::bundle::{AirGappedOrgBundle, BundledKel};
use crate::domains::org::error::OrgError;
use crate::domains::org::offboarding::find_revocation_event;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct OfflineVerifyReport {
pub org_did: IdentityDID,
pub as_of_org_seq: u128,
pub root_pinned: bool,
pub duplicity_detected: bool,
pub authority: Option<AuthorityAtSigning>,
}
fn check_kel_integrity(kel: &BundledKel) -> Result<(), OrgError> {
for event in &kel.events {
verify_event_said(event).map_err(|e| OrgError::BundleIntegrity {
id: kel.prefix.as_str().to_string(),
reason: e.to_string(),
})?;
}
Ok(())
}
fn is_delegated(org_kel: &[Event], member_prefix: &Prefix) -> bool {
org_kel.iter().any(|event| {
event.anchors().iter().any(
|seal| matches!(seal, Seal::KeyEvent { i, .. } if i.as_str() == member_prefix.as_str()),
)
})
}
pub fn classify_authority_in_bundle(
bundle: &AirGappedOrgBundle,
member_prefix: &Prefix,
signed_at: Option<u128>,
) -> AuthorityAtSigning {
classify_from_bundle(&bundle.org_kel.events, member_prefix, signed_at)
}
fn classify_from_bundle(
org_kel: &[Event],
member_prefix: &Prefix,
signed_at: Option<u128>,
) -> AuthorityAtSigning {
if !is_delegated(org_kel, member_prefix) {
return AuthorityAtSigning::NeverDelegated;
}
match find_revocation_event(org_kel, member_prefix) {
None => AuthorityAtSigning::AuthorizedBeforeRevocation,
Some((_, revoked_at)) => match signed_at {
None => AuthorityAtSigning::RejectedRevokedPositionUnknown { revoked_at },
Some(seq) if seq < revoked_at => AuthorityAtSigning::AuthorizedBeforeRevocation,
Some(_) => AuthorityAtSigning::RejectedAfterRevocation { revoked_at },
},
}
}
pub fn verify_org_bundle(
bundle: &AirGappedOrgBundle,
pinned_roots: &[IdentityDID],
query: Option<(&Prefix, Option<u128>)>,
) -> Result<OfflineVerifyReport, OrgError> {
check_kel_integrity(&bundle.org_kel)?;
for member_kel in &bundle.member_kels {
check_kel_integrity(member_kel)?;
}
for event in &bundle.org_kel.events {
for seal in event.anchors() {
if let Seal::KeyEvent { i, .. } = seal {
let present = bundle
.member_kels
.iter()
.any(|m| m.prefix.as_str() == i.as_str());
if !present {
return Err(OrgError::BundleMissingMemberKel {
member: format!("did:keri:{}", i.as_str()),
});
}
}
}
}
let root_pinned = pinned_roots
.iter()
.any(|r| r.as_str() == bundle.org_did.as_str());
let org_did_str = bundle.org_did.as_str().to_string();
let refs: Vec<KelEventRef<'_>> = bundle
.org_kel
.events
.iter()
.map(|e| KelEventRef {
prefix: org_did_str.as_str(),
seq: e.sequence().value() as u64,
said: e.said().as_str(),
})
.collect();
let duplicity_detected = detect_duplicity(&refs).is_diverging();
let authority = match query {
Some((member_prefix, signed_at)) => {
if !is_delegated(&bundle.org_kel.events, member_prefix) {
return Err(OrgError::BundleMissingDelegatorSeal {
member: format!("did:keri:{}", member_prefix.as_str()),
});
}
Some(classify_from_bundle(
&bundle.org_kel.events,
member_prefix,
signed_at,
))
}
None => None,
};
Ok(OfflineVerifyReport {
org_did: bundle.org_did.clone(),
as_of_org_seq: bundle.built_at_org_seq,
root_pinned,
duplicity_detected,
authority,
})
}