use auths_verifier::{CanonicalDid, IdentityDID, Role};
use serde::{Deserialize, Serialize};
use crate::checkpoint::SignedCheckpoint;
use crate::entry::{Entry, EntryType};
use crate::proof::InclusionProof;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct OfflineBundle {
pub entry: Entry,
pub inclusion_proof: InclusionProof,
pub signed_checkpoint: SignedCheckpoint,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub delegation_chain: Vec<DelegationChainLink>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct DelegationChainLink {
pub link_type: EntryType,
pub entry: Entry,
pub inclusion_proof: InclusionProof,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct BundleVerificationReport {
pub signature: SignatureStatus,
pub inclusion: InclusionStatus,
pub checkpoint: CheckpointStatus,
pub witnesses: WitnessStatus,
pub namespace: NamespaceStatus,
pub delegation: DelegationStatus,
pub warnings: Vec<String>,
}
impl BundleVerificationReport {
pub fn is_valid(&self) -> bool {
let sig_ok = matches!(self.signature, SignatureStatus::Verified);
let inc_ok = matches!(self.inclusion, InclusionStatus::Verified);
let chk_ok = matches!(
self.checkpoint,
CheckpointStatus::Verified | CheckpointStatus::NotProvided
);
let wit_ok = matches!(
self.witnesses,
WitnessStatus::Quorum { .. } | WitnessStatus::NotProvided
);
let ns_ok = matches!(
self.namespace,
NamespaceStatus::Authorized | NamespaceStatus::Owned
);
let del_ok = !matches!(self.delegation, DelegationStatus::ChainBroken { .. });
sig_ok && inc_ok && chk_ok && wit_ok && ns_ok && del_ok
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
#[non_exhaustive]
pub enum SignatureStatus {
Verified,
Failed {
reason: String,
},
NotProvided,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
#[non_exhaustive]
pub enum InclusionStatus {
Verified,
Failed {
reason: String,
},
NotProvided,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
#[non_exhaustive]
pub enum CheckpointStatus {
Verified,
InvalidSignature,
MissingEcdsaSignature,
MissingEcdsaKey,
NotProvided,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
#[non_exhaustive]
pub enum WitnessStatus {
Quorum {
verified: usize,
required: usize,
},
Insufficient {
verified: usize,
required: usize,
},
NotIndependent {
verified: usize,
required: usize,
shortfalls: Vec<String>,
},
NotProvided,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
#[non_exhaustive]
pub enum NamespaceStatus {
Authorized,
Owned,
Unowned,
Unauthorized,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
#[non_exhaustive]
pub enum DelegationStatus {
Direct,
ChainVerified {
org_did: IdentityDID,
member_did: IdentityDID,
member_role: Role,
device_did: CanonicalDid,
},
ChainBroken {
reason: String,
},
NoDelegationData,
}
#[cfg(test)]
#[allow(clippy::disallowed_methods)]
mod tests {
use super::*;
#[test]
fn signature_status_serializes() {
let status = SignatureStatus::Verified;
let json = serde_json::to_string(&status).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["status"], "verified");
}
#[test]
fn witness_status_quorum_serializes() {
let status = WitnessStatus::Quorum {
verified: 3,
required: 2,
};
let json = serde_json::to_string(&status).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["status"], "quorum");
assert_eq!(parsed["verified"], 3);
assert_eq!(parsed["required"], 2);
}
#[test]
fn witness_status_insufficient_serializes() {
let status = WitnessStatus::Insufficient {
verified: 1,
required: 3,
};
let json = serde_json::to_string(&status).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["status"], "insufficient");
assert_eq!(parsed["verified"], 1);
assert_eq!(parsed["required"], 3);
}
#[test]
fn report_is_valid_all_verified() {
let report = BundleVerificationReport {
signature: SignatureStatus::Verified,
inclusion: InclusionStatus::Verified,
checkpoint: CheckpointStatus::Verified,
witnesses: WitnessStatus::Quorum {
verified: 2,
required: 2,
},
namespace: NamespaceStatus::Authorized,
delegation: DelegationStatus::Direct,
warnings: vec![],
};
assert!(report.is_valid());
}
#[test]
fn report_is_valid_with_not_provided_optionals() {
let report = BundleVerificationReport {
signature: SignatureStatus::Verified,
inclusion: InclusionStatus::Verified,
checkpoint: CheckpointStatus::NotProvided,
witnesses: WitnessStatus::NotProvided,
namespace: NamespaceStatus::Owned,
delegation: DelegationStatus::NoDelegationData,
warnings: vec![],
};
assert!(report.is_valid());
}
#[test]
fn report_invalid_on_failed_signature() {
let report = BundleVerificationReport {
signature: SignatureStatus::Failed {
reason: "bad sig".into(),
},
inclusion: InclusionStatus::Verified,
checkpoint: CheckpointStatus::Verified,
witnesses: WitnessStatus::NotProvided,
namespace: NamespaceStatus::Authorized,
delegation: DelegationStatus::Direct,
warnings: vec![],
};
assert!(!report.is_valid());
}
#[test]
fn report_invalid_on_broken_delegation() {
let report = BundleVerificationReport {
signature: SignatureStatus::Verified,
inclusion: InclusionStatus::Verified,
checkpoint: CheckpointStatus::Verified,
witnesses: WitnessStatus::NotProvided,
namespace: NamespaceStatus::Authorized,
delegation: DelegationStatus::ChainBroken {
reason: "missing link".into(),
},
warnings: vec![],
};
assert!(!report.is_valid());
}
#[test]
fn delegation_status_chain_verified_serializes() {
let status = DelegationStatus::ChainVerified {
org_did: IdentityDID::new_unchecked("did:keri:EOrg123"),
member_did: IdentityDID::new_unchecked("did:keri:EMember456"),
member_role: Role::Admin,
device_did: CanonicalDid::new_unchecked("did:key:z6MkDevice789"),
};
let json = serde_json::to_string(&status).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["status"], "chain_verified");
assert_eq!(parsed["org_did"], "did:keri:EOrg123");
assert_eq!(parsed["member_did"], "did:keri:EMember456");
}
}