Skip to main content

auths_verifier/
verify.rs

1//! Free-function verification API wrapping [`crate::verifier::Verifier`].
2
3#[cfg(feature = "native")]
4use crate::core::Capability;
5use crate::core::{
6    Attestation, CanonicalAttestationData, VerifiedAttestation, canonicalize_attestation_data,
7};
8use crate::error::AttestationError;
9use crate::types::{ChainLink, VerificationReport, VerificationStatus};
10#[cfg(feature = "native")]
11use crate::witness::WitnessVerifyConfig;
12use auths_crypto::{CryptoProvider, ED25519_PUBLIC_KEY_LEN};
13use chrono::{DateTime, Duration, Utc};
14use log::debug;
15use serde::Serialize;
16
17/// Maximum allowed clock skew in seconds for timestamp validation.
18const MAX_SKEW_SECS: i64 = 5 * 60;
19
20// ---------------------------------------------------------------------------
21// Public free functions — backward-compatible, #[cfg(feature = "native")]
22// ---------------------------------------------------------------------------
23
24/// Verify an attestation's signatures against the issuer's public key.
25///
26/// Args:
27/// * `att`: The attestation to verify.
28/// * `issuer_pk_bytes`: Raw Ed25519 public key of the issuer.
29#[cfg(feature = "native")]
30pub async fn verify_with_keys(
31    att: &Attestation,
32    issuer_pk_bytes: &[u8],
33) -> Result<VerifiedAttestation, AttestationError> {
34    crate::verifier::Verifier::native()
35        .verify_with_keys(att, issuer_pk_bytes)
36        .await
37}
38
39/// Verify an attestation and check that it grants a required capability.
40///
41/// Args:
42/// * `att`: The attestation to verify.
43/// * `required`: The capability that must be present.
44/// * `issuer_pk_bytes`: Raw Ed25519 public key of the issuer.
45#[cfg(feature = "native")]
46pub async fn verify_with_capability(
47    att: &Attestation,
48    required: &Capability,
49    issuer_pk_bytes: &[u8],
50) -> Result<VerifiedAttestation, AttestationError> {
51    crate::verifier::Verifier::native()
52        .verify_with_capability(att, required, issuer_pk_bytes)
53        .await
54}
55
56/// Verify a chain and assert that all attestations share a required capability.
57///
58/// Args:
59/// * `attestations`: Ordered attestation chain (root first).
60/// * `required`: The capability that must appear in every link.
61/// * `root_pk`: Raw Ed25519 public key of the root identity.
62#[cfg(feature = "native")]
63pub async fn verify_chain_with_capability(
64    attestations: &[Attestation],
65    required: &Capability,
66    root_pk: &[u8],
67) -> Result<VerificationReport, AttestationError> {
68    crate::verifier::Verifier::native()
69        .verify_chain_with_capability(attestations, required, root_pk)
70        .await
71}
72
73/// Verify an attestation against a specific point in time.
74///
75/// Args:
76/// * `att`: The attestation to verify.
77/// * `issuer_pk_bytes`: Raw Ed25519 public key of the issuer.
78/// * `at`: The reference timestamp for expiry evaluation.
79#[cfg(feature = "native")]
80pub async fn verify_at_time(
81    att: &Attestation,
82    issuer_pk_bytes: &[u8],
83    at: DateTime<Utc>,
84) -> Result<VerifiedAttestation, AttestationError> {
85    crate::verifier::Verifier::native()
86        .verify_at_time(att, issuer_pk_bytes, at)
87        .await
88}
89
90/// Verify a chain and validate witness receipts against a quorum threshold.
91///
92/// Args:
93/// * `attestations`: Ordered attestation chain (root first).
94/// * `root_pk`: Raw Ed25519 public key of the root identity.
95/// * `witness_config`: Witness receipts and quorum threshold to validate.
96#[cfg(feature = "native")]
97pub async fn verify_chain_with_witnesses(
98    attestations: &[Attestation],
99    root_pk: &[u8],
100    witness_config: &WitnessVerifyConfig<'_>,
101) -> Result<VerificationReport, AttestationError> {
102    crate::verifier::Verifier::native()
103        .verify_chain_with_witnesses(attestations, root_pk, witness_config)
104        .await
105}
106
107/// Verify an ordered attestation chain starting from a known root public key.
108///
109/// Args:
110/// * `attestations`: Ordered attestation chain (root first).
111/// * `root_pk`: Raw Ed25519 public key of the root identity.
112#[cfg(feature = "native")]
113pub async fn verify_chain(
114    attestations: &[Attestation],
115    root_pk: &[u8],
116) -> Result<VerificationReport, AttestationError> {
117    crate::verifier::Verifier::native()
118        .verify_chain(attestations, root_pk)
119        .await
120}
121
122/// Verify that a device is authorized under a given identity.
123///
124/// Args:
125/// * `identity_did`: The DID of the authorizing identity.
126/// * `device_did`: The device DID to check authorization for.
127/// * `attestations`: Pool of attestations to search.
128/// * `identity_pk`: Raw Ed25519 public key of the identity.
129#[cfg(feature = "native")]
130pub async fn verify_device_authorization(
131    identity_did: &str,
132    device_did: &crate::types::DeviceDID,
133    attestations: &[Attestation],
134    identity_pk: &[u8],
135) -> Result<VerificationReport, AttestationError> {
136    crate::verifier::Verifier::native()
137        .verify_device_authorization(identity_did, device_did, attestations, identity_pk)
138        .await
139}
140
141/// Resolve a `did:key:` DID to raw Ed25519 public key bytes.
142///
143/// Args:
144/// * `did`: A DID string in `did:key:z...` format. KERI DIDs (`did:keri:`) contain
145///   opaque SAIDs (not encoded keys) and require external key state resolution.
146///
147/// Usage:
148/// ```ignore
149/// let pk_bytes = did_to_ed25519("did:key:z6Mkf...")?;
150/// ```
151pub fn did_to_ed25519(did: &str) -> Result<Vec<u8>, AttestationError> {
152    if did.starts_with("did:key:") {
153        auths_crypto::did_key_to_ed25519(did)
154            .map(|k| k.to_vec())
155            .map_err(|e| AttestationError::DidResolutionError(e.to_string()))
156    } else {
157        Err(AttestationError::DidResolutionError(format!(
158            "Cannot extract key from DID method (requires external resolution): {}",
159            did
160        )))
161    }
162}
163
164use crate::types::DeviceDID;
165
166/// Checks if a device appears in a list of **already-verified** attestations.
167pub fn is_device_listed(
168    identity_did: &str,
169    device_did: &DeviceDID,
170    attestations: &[VerifiedAttestation],
171    now: DateTime<Utc>,
172) -> bool {
173    let device_did_str = device_did.to_string();
174
175    attestations.iter().any(|verified| {
176        let att = verified.inner();
177        if att.issuer != identity_did {
178            return false;
179        }
180        if att.subject.to_string() != device_did_str {
181            return false;
182        }
183        if att.is_revoked() {
184            return false;
185        }
186        if let Some(exp) = att.expires_at
187            && now > exp
188        {
189            return false;
190        }
191        true
192    })
193}
194
195// ---------------------------------------------------------------------------
196// Device-link verification — stateless, provider-agnostic
197// ---------------------------------------------------------------------------
198
199/// Result of verifying a device's link to a KERI identity.
200///
201/// Verification failures are expressed as `valid: false` with an error message,
202/// not as Rust `Err` values. Only infrastructure errors (bad input, serialization)
203/// produce `Err`.
204#[derive(Debug, Clone, Serialize)]
205pub struct DeviceLinkVerification {
206    /// Whether the device link verified successfully.
207    pub valid: bool,
208    /// Human-readable reason if verification failed.
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub error: Option<String>,
211    /// The KERI key state after KEL replay (present on success).
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub key_state: Option<crate::keri::KeriKeyState>,
214    /// Sequence number of the IXN event anchoring the attestation seal (if found).
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub seal_sequence: Option<u64>,
217}
218
219impl DeviceLinkVerification {
220    fn success(key_state: crate::keri::KeriKeyState, seal_sequence: Option<u64>) -> Self {
221        Self {
222            valid: true,
223            error: None,
224            key_state: Some(key_state),
225            seal_sequence,
226        }
227    }
228
229    fn failure(reason: impl Into<String>) -> Self {
230        Self {
231            valid: false,
232            error: Some(reason.into()),
233            key_state: None,
234            seal_sequence: None,
235        }
236    }
237}
238
239/// Verify that a device (`did:key`) is cryptographically linked to a KERI identity.
240///
241/// Composes: KEL verification, attestation signature verification, device DID matching,
242/// and optional seal anchoring. Provider-agnostic — works with any `CryptoProvider`.
243///
244/// Args:
245/// * `events`: Parsed KEL events (inception first).
246/// * `attestation`: The attestation linking identity to device.
247/// * `device_did`: The expected device DID (checked against attestation subject).
248/// * `now`: Reference time for expiration/revocation checks.
249/// * `provider`: Cryptographic provider for Ed25519 verification.
250///
251/// Usage:
252/// ```ignore
253/// let result = verify_device_link(&events, &att, "did:key:z6Mk...", now, &provider).await;
254/// if result.valid { /* ... */ }
255/// ```
256pub async fn verify_device_link(
257    events: &[crate::keri::KeriEvent],
258    attestation: &Attestation,
259    device_did: &str,
260    now: DateTime<Utc>,
261    provider: &dyn CryptoProvider,
262) -> DeviceLinkVerification {
263    let key_state = match crate::keri::verify_kel(events, provider).await {
264        Ok(ks) => ks,
265        Err(e) => return DeviceLinkVerification::failure(format!("KEL verification failed: {e}")),
266    };
267
268    if attestation.subject.to_string() != device_did {
269        return DeviceLinkVerification::failure(format!(
270            "Device DID mismatch: attestation subject is '{}', expected '{device_did}'",
271            attestation.subject
272        ));
273    }
274
275    if let Err(e) =
276        verify_with_keys_at(attestation, &key_state.current_key, now, true, provider).await
277    {
278        return DeviceLinkVerification::failure(format!("Attestation verification failed: {e}"));
279    }
280
281    let seal_sequence = compute_attestation_seal_digest(attestation)
282        .ok()
283        .and_then(|digest| crate::keri::find_seal_in_kel(events, digest.as_str()));
284
285    DeviceLinkVerification::success(key_state, seal_sequence)
286}
287
288/// Compute the KERI SAID (Blake3 digest) of an attestation's canonical form.
289///
290/// This is the digest that should appear in a KEL IXN seal when the attestation
291/// is anchored. Returns the SAID string (E-prefixed base64url Blake3).
292pub fn compute_attestation_seal_digest(
293    attestation: &Attestation,
294) -> Result<String, AttestationError> {
295    let data = CanonicalAttestationData {
296        version: attestation.version,
297        rid: &attestation.rid,
298        issuer: &attestation.issuer,
299        subject: &attestation.subject,
300        device_public_key: attestation.device_public_key.as_bytes(),
301        payload: &attestation.payload,
302        timestamp: &attestation.timestamp,
303        expires_at: &attestation.expires_at,
304        revoked_at: &attestation.revoked_at,
305        note: &attestation.note,
306        role: attestation.role.as_ref().map(|r| r.as_str()),
307        capabilities: if attestation.capabilities.is_empty() {
308            None
309        } else {
310            Some(&attestation.capabilities)
311        },
312        delegated_by: attestation.delegated_by.as_ref(),
313        signer_type: attestation.signer_type.as_ref(),
314    };
315    let canonical = canonicalize_attestation_data(&data)?;
316    Ok(crate::keri::compute_said(&canonical).to_string())
317}
318
319// ---------------------------------------------------------------------------
320// Internal async functions — used by Verifier and free function wrappers
321// ---------------------------------------------------------------------------
322
323pub(crate) async fn verify_with_keys_at(
324    att: &Attestation,
325    issuer_pk_bytes: &[u8],
326    at: DateTime<Utc>,
327    check_skew: bool,
328    provider: &dyn CryptoProvider,
329) -> Result<(), AttestationError> {
330    let reference_time = at;
331
332    // --- 1. Check revocation (time-aware) ---
333    if let Some(revoked_at) = att.revoked_at
334        && revoked_at <= reference_time
335    {
336        return Err(AttestationError::VerificationError(
337            "Attestation revoked".to_string(),
338        ));
339    }
340
341    // --- 2. Check expiration against reference time ---
342    if let Some(exp) = att.expires_at
343        && reference_time > exp
344    {
345        return Err(AttestationError::VerificationError(format!(
346            "Attestation expired on {}",
347            exp.to_rfc3339()
348        )));
349    }
350
351    // --- 3. Check timestamp skew against reference time ---
352    if check_skew
353        && let Some(ts) = att.timestamp
354        && ts > reference_time + Duration::seconds(MAX_SKEW_SECS)
355    {
356        return Err(AttestationError::VerificationError(format!(
357            "Attestation timestamp ({}) is in the future",
358            ts.to_rfc3339(),
359        )));
360    }
361
362    // --- 4. Check provided issuer public key length ---
363    if !att.identity_signature.is_empty() && issuer_pk_bytes.len() != ED25519_PUBLIC_KEY_LEN {
364        return Err(AttestationError::VerificationError(format!(
365            "Provided issuer public key has invalid length: {}",
366            issuer_pk_bytes.len()
367        )));
368    }
369
370    // --- 5. Reconstruct and canonicalize data ---
371    let data_to_canonicalize = CanonicalAttestationData {
372        version: att.version,
373        rid: &att.rid,
374        issuer: &att.issuer,
375        subject: &att.subject,
376        device_public_key: att.device_public_key.as_bytes(),
377        payload: &att.payload,
378        timestamp: &att.timestamp,
379        expires_at: &att.expires_at,
380        revoked_at: &att.revoked_at,
381        note: &att.note,
382        role: att.role.as_ref().map(|r| r.as_str()),
383        capabilities: if att.capabilities.is_empty() {
384            None
385        } else {
386            Some(&att.capabilities)
387        },
388        delegated_by: att.delegated_by.as_ref(),
389        signer_type: att.signer_type.as_ref(),
390    };
391    let canonical_json_bytes = canonicalize_attestation_data(&data_to_canonicalize)?;
392    let data_to_verify = canonical_json_bytes.as_slice();
393    debug!(
394        "(Verify) Canonical data: {}",
395        String::from_utf8_lossy(&canonical_json_bytes)
396    );
397
398    // --- 6. Verify issuer signature ---
399    if !att.identity_signature.is_empty() {
400        provider
401            .verify_ed25519(
402                issuer_pk_bytes,
403                data_to_verify,
404                att.identity_signature.as_bytes(),
405            )
406            .await
407            .map_err(|_| {
408                AttestationError::VerificationError(
409                    "Issuer signature verification failed".to_string(),
410                )
411            })?;
412        debug!("(Verify) Issuer signature verified successfully.");
413    } else {
414        debug!(
415            "(Verify) No identity signature present (device-only attestation), skipping issuer check."
416        );
417    }
418
419    // --- 7. Verify device signature ---
420    provider
421        .verify_ed25519(
422            att.device_public_key.as_bytes(),
423            data_to_verify,
424            att.device_signature.as_bytes(),
425        )
426        .await
427        .map_err(|_| {
428            AttestationError::VerificationError("Device signature verification failed".to_string())
429        })?;
430    debug!("(Verify) Device signature verified successfully.");
431
432    Ok(())
433}
434
435pub(crate) async fn verify_chain_inner(
436    attestations: &[Attestation],
437    root_pk: &[u8],
438    provider: &dyn CryptoProvider,
439    now: DateTime<Utc>,
440) -> Result<VerificationReport, AttestationError> {
441    if attestations.is_empty() {
442        return Ok(VerificationReport::with_status(
443            VerificationStatus::BrokenChain {
444                missing_link: "empty chain".to_string(),
445            },
446            vec![],
447        ));
448    }
449
450    let mut chain_links: Vec<ChainLink> = Vec::with_capacity(attestations.len());
451
452    let first_att = &attestations[0];
453    match verify_single_attestation(first_att, root_pk, 0, provider, now).await {
454        Ok(link) => chain_links.push(link),
455        Err((status, link)) => {
456            chain_links.push(link);
457            return Ok(VerificationReport::with_status(status, chain_links));
458        }
459    }
460
461    for (idx, att) in attestations.iter().enumerate().skip(1) {
462        let prev_att = &attestations[idx - 1];
463
464        if att.issuer.as_str() != prev_att.subject.as_str() {
465            let link = ChainLink::invalid(
466                att.issuer.to_string(),
467                att.subject.to_string(),
468                format!(
469                    "Chain broken: expected issuer '{}', got '{}'",
470                    prev_att.subject, att.issuer
471                ),
472            );
473            chain_links.push(link);
474            return Ok(VerificationReport::with_status(
475                VerificationStatus::BrokenChain {
476                    missing_link: format!(
477                        "Issuer mismatch at step {}: expected '{}', got '{}'",
478                        idx, prev_att.subject, att.issuer
479                    ),
480                },
481                chain_links,
482            ));
483        }
484
485        let issuer_pk = prev_att.device_public_key.as_bytes().as_slice();
486
487        match verify_single_attestation(att, issuer_pk, idx, provider, now).await {
488            Ok(link) => chain_links.push(link),
489            Err((status, link)) => {
490                chain_links.push(link);
491                return Ok(VerificationReport::with_status(status, chain_links));
492            }
493        }
494    }
495
496    Ok(VerificationReport::valid(chain_links))
497}
498
499pub(crate) async fn verify_device_authorization_inner(
500    identity_did: &str,
501    device_did: &DeviceDID,
502    attestations: &[Attestation],
503    identity_pk: &[u8],
504    provider: &dyn CryptoProvider,
505    now: DateTime<Utc>,
506) -> Result<VerificationReport, AttestationError> {
507    let device_did_str = device_did.to_string();
508
509    let matching: Vec<&Attestation> = attestations
510        .iter()
511        .filter(|a| a.issuer == identity_did && a.subject.to_string() == device_did_str)
512        .collect();
513
514    if matching.is_empty() {
515        return Ok(VerificationReport::with_status(
516            VerificationStatus::BrokenChain {
517                missing_link: format!(
518                    "No attestation found for device {} under {}",
519                    device_did_str, identity_did
520                ),
521            },
522            vec![],
523        ));
524    }
525
526    match verify_single_attestation(matching[0], identity_pk, 0, provider, now).await {
527        Ok(link) => Ok(VerificationReport::valid(vec![link])),
528        Err((status, link)) => Ok(VerificationReport::with_status(status, vec![link])),
529    }
530}
531
532async fn verify_single_attestation(
533    att: &Attestation,
534    issuer_pk: &[u8],
535    step: usize,
536    provider: &dyn CryptoProvider,
537    now: DateTime<Utc>,
538) -> Result<ChainLink, (VerificationStatus, ChainLink)> {
539    let issuer = att.issuer.to_string();
540    let subject = att.subject.to_string();
541
542    if att.is_revoked() {
543        return Err((
544            VerificationStatus::Revoked { at: att.revoked_at },
545            ChainLink::invalid(issuer, subject, "Attestation revoked".to_string()),
546        ));
547    }
548
549    if let Some(exp) = att.expires_at
550        && now > exp
551    {
552        return Err((
553            VerificationStatus::Expired { at: exp },
554            ChainLink::invalid(
555                issuer,
556                subject,
557                format!("Attestation expired on {}", exp.to_rfc3339()),
558            ),
559        ));
560    }
561
562    match verify_with_keys_at(att, issuer_pk, now, true, provider).await {
563        Ok(()) => Ok(ChainLink::valid(issuer, subject)),
564        Err(e) => Err((
565            VerificationStatus::InvalidSignature { step },
566            ChainLink::invalid(issuer, subject, e.to_string()),
567        )),
568    }
569}
570
571#[cfg(all(test, not(target_arch = "wasm32")))]
572mod tests {
573    use super::*;
574    use crate::clock::ClockProvider;
575    use crate::core::{Capability, Ed25519PublicKey, Ed25519Signature, ResourceId, Role};
576    use crate::keri::Said;
577    use crate::types::{DeviceDID, IdentityDID};
578    use crate::verifier::Verifier;
579    use auths_crypto::RingCryptoProvider;
580    use auths_crypto::testing::create_test_keypair;
581    use chrono::{DateTime, Duration, TimeZone, Utc};
582    use ring::signature::{Ed25519KeyPair, KeyPair};
583    use std::sync::Arc;
584
585    struct TestClock(DateTime<Utc>);
586    impl ClockProvider for TestClock {
587        fn now(&self) -> DateTime<Utc> {
588            self.0
589        }
590    }
591
592    fn fixed_now() -> DateTime<Utc> {
593        Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap()
594    }
595
596    fn test_verifier() -> Verifier {
597        Verifier::new(
598            Arc::new(RingCryptoProvider),
599            Arc::new(TestClock(fixed_now())),
600        )
601    }
602
603    /// Helper to create a signed attestation
604    fn create_signed_attestation(
605        issuer_kp: &Ed25519KeyPair,
606        device_kp: &Ed25519KeyPair,
607        issuer_did: &str,
608        subject_did: &str,
609        revoked_at: Option<DateTime<Utc>>,
610        expires_at: Option<DateTime<Utc>>,
611    ) -> Attestation {
612        let device_pk: [u8; 32] = device_kp.public_key().as_ref().try_into().unwrap();
613
614        let mut att = Attestation {
615            version: 1,
616            rid: ResourceId::new("test-rid"),
617            issuer: IdentityDID::new(issuer_did),
618            subject: DeviceDID::new(subject_did),
619            device_public_key: Ed25519PublicKey::from_bytes(device_pk),
620            identity_signature: Ed25519Signature::empty(),
621            device_signature: Ed25519Signature::empty(),
622            revoked_at,
623            expires_at,
624            timestamp: Some(fixed_now()),
625            note: None,
626            payload: None,
627            role: None,
628            capabilities: vec![],
629            delegated_by: None,
630            signer_type: None,
631        };
632
633        let data = CanonicalAttestationData {
634            version: att.version,
635            rid: &att.rid,
636            issuer: &att.issuer,
637            subject: &att.subject,
638            device_public_key: att.device_public_key.as_bytes(),
639            payload: &att.payload,
640            timestamp: &att.timestamp,
641            expires_at: &att.expires_at,
642            revoked_at: &att.revoked_at,
643            note: &att.note,
644            role: att.role.as_ref().map(|r| r.as_str()),
645            capabilities: if att.capabilities.is_empty() {
646                None
647            } else {
648                Some(&att.capabilities)
649            },
650            delegated_by: att.delegated_by.as_ref(),
651            signer_type: att.signer_type.as_ref(),
652        };
653        let canonical_bytes = canonicalize_attestation_data(&data).unwrap();
654
655        att.identity_signature =
656            Ed25519Signature::try_from_slice(issuer_kp.sign(&canonical_bytes).as_ref()).unwrap();
657        att.device_signature =
658            Ed25519Signature::try_from_slice(device_kp.sign(&canonical_bytes).as_ref()).unwrap();
659
660        att
661    }
662
663    #[tokio::test]
664    async fn verify_chain_empty_returns_broken_chain() {
665        let result = test_verifier().verify_chain(&[], &[0u8; 32]).await.unwrap();
666        assert!(!result.is_valid());
667        match result.status {
668            VerificationStatus::BrokenChain { missing_link } => {
669                assert_eq!(missing_link, "empty chain");
670            }
671            _ => panic!("Expected BrokenChain status"),
672        }
673        assert!(result.chain.is_empty());
674    }
675
676    #[tokio::test]
677    async fn verify_chain_single_valid_attestation() {
678        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
679        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
680        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
681        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
682
683        let att = create_signed_attestation(
684            &root_kp,
685            &device_kp,
686            &root_did,
687            &device_did,
688            None,
689            Some(fixed_now() + Duration::days(365)),
690        );
691
692        let result = test_verifier()
693            .verify_chain(&[att], &root_pk)
694            .await
695            .unwrap();
696        assert!(result.is_valid());
697        assert_eq!(result.chain.len(), 1);
698        assert!(result.chain[0].valid);
699    }
700
701    #[tokio::test]
702    async fn verify_chain_revoked_attestation_returns_revoked() {
703        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
704        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
705        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
706        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
707
708        let att = create_signed_attestation(
709            &root_kp,
710            &device_kp,
711            &root_did,
712            &device_did,
713            Some(fixed_now()),
714            Some(fixed_now() + Duration::days(365)),
715        );
716
717        let result = test_verifier()
718            .verify_chain(&[att], &root_pk)
719            .await
720            .unwrap();
721        assert!(!result.is_valid());
722        match result.status {
723            VerificationStatus::Revoked { .. } => {}
724            _ => panic!("Expected Revoked status, got {:?}", result.status),
725        }
726    }
727
728    #[tokio::test]
729    async fn verify_chain_expired_attestation_returns_expired() {
730        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
731        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
732        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
733        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
734
735        let att = create_signed_attestation(
736            &root_kp,
737            &device_kp,
738            &root_did,
739            &device_did,
740            None,
741            Some(fixed_now() - Duration::days(1)),
742        );
743
744        let result = test_verifier()
745            .verify_chain(&[att], &root_pk)
746            .await
747            .unwrap();
748        assert!(!result.is_valid());
749        match result.status {
750            VerificationStatus::Expired { .. } => {}
751            _ => panic!("Expected Expired status, got {:?}", result.status),
752        }
753    }
754
755    #[tokio::test]
756    async fn verify_chain_invalid_signature_returns_invalid_signature() {
757        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
758        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
759        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
760        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
761
762        let mut att = create_signed_attestation(
763            &root_kp,
764            &device_kp,
765            &root_did,
766            &device_did,
767            None,
768            Some(fixed_now() + Duration::days(365)),
769        );
770        let mut tampered = *att.identity_signature.as_bytes();
771        tampered[0] ^= 0xFF;
772        att.identity_signature = Ed25519Signature::from_bytes(tampered);
773
774        let result = test_verifier()
775            .verify_chain(&[att], &root_pk)
776            .await
777            .unwrap();
778        assert!(!result.is_valid());
779        match result.status {
780            VerificationStatus::InvalidSignature { step } => {
781                assert_eq!(step, 0);
782            }
783            _ => panic!("Expected InvalidSignature status, got {:?}", result.status),
784        }
785    }
786
787    #[tokio::test]
788    async fn verify_chain_broken_link_returns_broken_chain() {
789        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
790        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
791        let (device1_kp, device1_pk) = create_test_keypair(&[2u8; 32]);
792        let device1_did = auths_crypto::ed25519_pubkey_to_did_key(&device1_pk);
793        let (device2_kp, device2_pk) = create_test_keypair(&[3u8; 32]);
794        let device2_did = auths_crypto::ed25519_pubkey_to_did_key(&device2_pk);
795
796        let att1 = create_signed_attestation(
797            &root_kp,
798            &device1_kp,
799            &root_did,
800            &device1_did,
801            None,
802            Some(fixed_now() + Duration::days(365)),
803        );
804        let att2 = create_signed_attestation(
805            &device1_kp,
806            &device2_kp,
807            &root_did, // WRONG: should be device1_did
808            &device2_did,
809            None,
810            Some(fixed_now() + Duration::days(365)),
811        );
812
813        let result = test_verifier()
814            .verify_chain(&[att1, att2], &root_pk)
815            .await
816            .unwrap();
817        assert!(!result.is_valid());
818        match result.status {
819            VerificationStatus::BrokenChain { missing_link } => {
820                assert!(missing_link.contains("Issuer mismatch"));
821            }
822            _ => panic!("Expected BrokenChain status, got {:?}", result.status),
823        }
824    }
825
826    #[tokio::test]
827    async fn verify_chain_valid_three_level_chain() {
828        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
829        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
830        let (identity_kp, identity_pk) = create_test_keypair(&[2u8; 32]);
831        let identity_did = auths_crypto::ed25519_pubkey_to_did_key(&identity_pk);
832        let (device_kp, device_pk) = create_test_keypair(&[3u8; 32]);
833        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
834
835        let att1 = create_signed_attestation(
836            &root_kp,
837            &identity_kp,
838            &root_did,
839            &identity_did,
840            None,
841            Some(fixed_now() + Duration::days(365)),
842        );
843        let att2 = create_signed_attestation(
844            &identity_kp,
845            &device_kp,
846            &identity_did,
847            &device_did,
848            None,
849            Some(fixed_now() + Duration::days(365)),
850        );
851
852        let result = test_verifier()
853            .verify_chain(&[att1, att2], &root_pk)
854            .await
855            .unwrap();
856        assert!(result.is_valid());
857        assert_eq!(result.chain.len(), 2);
858        assert!(result.chain[0].valid);
859        assert!(result.chain[1].valid);
860    }
861
862    #[tokio::test]
863    async fn verify_chain_revoked_intermediate_returns_revoked() {
864        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
865        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
866        let (identity_kp, identity_pk) = create_test_keypair(&[2u8; 32]);
867        let identity_did = auths_crypto::ed25519_pubkey_to_did_key(&identity_pk);
868        let (device_kp, device_pk) = create_test_keypair(&[3u8; 32]);
869        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
870
871        let att1 = create_signed_attestation(
872            &root_kp,
873            &identity_kp,
874            &root_did,
875            &identity_did,
876            None,
877            Some(fixed_now() + Duration::days(365)),
878        );
879        let att2 = create_signed_attestation(
880            &identity_kp,
881            &device_kp,
882            &identity_did,
883            &device_did,
884            Some(fixed_now()),
885            Some(fixed_now() + Duration::days(365)),
886        );
887
888        let result = test_verifier()
889            .verify_chain(&[att1, att2], &root_pk)
890            .await
891            .unwrap();
892        assert!(!result.is_valid());
893        match result.status {
894            VerificationStatus::Revoked { .. } => {}
895            _ => panic!("Expected Revoked status, got {:?}", result.status),
896        }
897        assert_eq!(result.chain.len(), 2);
898        assert!(result.chain[0].valid);
899        assert!(!result.chain[1].valid);
900    }
901
902    #[tokio::test]
903    async fn verify_at_time_valid_before_expiration() {
904        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
905        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
906        let (device_kp, _) = create_test_keypair(&[2u8; 32]);
907        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
908
909        let expires = fixed_now() + Duration::days(30);
910        let att = create_signed_attestation(
911            &root_kp,
912            &device_kp,
913            &root_did,
914            &device_did,
915            None,
916            Some(expires),
917        );
918
919        let verification_time = fixed_now() + Duration::days(10);
920        let result = test_verifier()
921            .verify_at_time(&att, &root_pk, verification_time)
922            .await;
923        assert!(result.is_ok());
924    }
925
926    #[tokio::test]
927    async fn verify_at_time_expired_after_expiration() {
928        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
929        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
930        let (device_kp, _) = create_test_keypair(&[2u8; 32]);
931        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
932
933        let expires = fixed_now() + Duration::days(30);
934        let att = create_signed_attestation(
935            &root_kp,
936            &device_kp,
937            &root_did,
938            &device_did,
939            None,
940            Some(expires),
941        );
942
943        let verification_time = fixed_now() + Duration::days(60);
944        let result = test_verifier()
945            .verify_at_time(&att, &root_pk, verification_time)
946            .await;
947        assert!(result.is_err());
948        assert!(result.unwrap_err().to_string().contains("expired"));
949    }
950
951    #[tokio::test]
952    async fn verify_at_time_signature_always_checked() {
953        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
954        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
955        let (device_kp, _) = create_test_keypair(&[2u8; 32]);
956        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
957
958        let mut att = create_signed_attestation(
959            &root_kp,
960            &device_kp,
961            &root_did,
962            &device_did,
963            None,
964            Some(fixed_now() + Duration::days(365)),
965        );
966        let mut tampered = *att.identity_signature.as_bytes();
967        tampered[0] ^= 0xFF;
968        att.identity_signature = Ed25519Signature::from_bytes(tampered);
969
970        let verification_time = fixed_now() - Duration::days(10);
971        let result = test_verifier()
972            .verify_at_time(&att, &root_pk, verification_time)
973            .await;
974        assert!(result.is_err());
975        assert!(
976            result
977                .unwrap_err()
978                .to_string()
979                .contains("signature verification failed")
980        );
981    }
982
983    #[tokio::test]
984    async fn verify_at_time_with_past_time_skips_skew_check() {
985        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
986        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
987        let (device_kp, _) = create_test_keypair(&[2u8; 32]);
988        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
989
990        let att = create_signed_attestation(
991            &root_kp,
992            &device_kp,
993            &root_did,
994            &device_did,
995            None,
996            Some(fixed_now() + Duration::days(365)),
997        );
998
999        let verification_time = fixed_now() - Duration::days(30);
1000        let result = test_verifier()
1001            .verify_at_time(&att, &root_pk, verification_time)
1002            .await;
1003        assert!(result.is_ok());
1004    }
1005
1006    #[tokio::test]
1007    async fn verify_with_keys_still_works() {
1008        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1009        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1010        let (device_kp, _) = create_test_keypair(&[2u8; 32]);
1011        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1012
1013        let att = create_signed_attestation(
1014            &root_kp,
1015            &device_kp,
1016            &root_did,
1017            &device_did,
1018            None,
1019            Some(fixed_now() + Duration::days(365)),
1020        );
1021
1022        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1023        assert!(result.is_ok());
1024    }
1025
1026    /// Helper to wrap an attestation as verified (for tests where we created it ourselves).
1027    fn verified(att: Attestation) -> VerifiedAttestation {
1028        VerifiedAttestation::dangerous_from_unchecked(att)
1029    }
1030
1031    #[test]
1032    fn is_device_listed_returns_true_for_valid_attestation() {
1033        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1034        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1035        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1036        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1037        let device_did = DeviceDID::new(&device_did_str);
1038
1039        let att = create_signed_attestation(
1040            &root_kp,
1041            &device_kp,
1042            &root_did,
1043            &device_did_str,
1044            None,
1045            Some(fixed_now() + Duration::days(365)),
1046        );
1047
1048        assert!(is_device_listed(
1049            &root_did,
1050            &device_did,
1051            &[verified(att)],
1052            fixed_now()
1053        ));
1054    }
1055
1056    #[test]
1057    fn is_device_listed_returns_false_for_no_attestations() {
1058        let (_, root_pk) = create_test_keypair(&[1u8; 32]);
1059        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1060        let (_, device_pk) = create_test_keypair(&[2u8; 32]);
1061        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1062        let device_did = DeviceDID::new(&device_did_str);
1063
1064        assert!(!is_device_listed(&root_did, &device_did, &[], fixed_now()));
1065    }
1066
1067    #[test]
1068    fn is_device_listed_returns_false_for_expired_attestation() {
1069        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1070        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1071        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1072        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1073        let device_did = DeviceDID::new(&device_did_str);
1074
1075        let att = create_signed_attestation(
1076            &root_kp,
1077            &device_kp,
1078            &root_did,
1079            &device_did_str,
1080            None,
1081            Some(fixed_now() - Duration::days(1)),
1082        );
1083
1084        assert!(!is_device_listed(
1085            &root_did,
1086            &device_did,
1087            &[verified(att)],
1088            fixed_now()
1089        ));
1090    }
1091
1092    #[test]
1093    fn is_device_listed_returns_false_for_revoked_attestation() {
1094        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1095        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1096        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1097        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1098        let device_did = DeviceDID::new(&device_did_str);
1099
1100        let att = create_signed_attestation(
1101            &root_kp,
1102            &device_kp,
1103            &root_did,
1104            &device_did_str,
1105            Some(fixed_now()),
1106            Some(fixed_now() + Duration::days(365)),
1107        );
1108
1109        assert!(!is_device_listed(
1110            &root_did,
1111            &device_did,
1112            &[verified(att)],
1113            fixed_now()
1114        ));
1115    }
1116
1117    #[test]
1118    fn is_device_listed_returns_true_if_one_valid_among_many() {
1119        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1120        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1121        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1122        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1123        let device_did = DeviceDID::new(&device_did_str);
1124
1125        let att_expired = verified(create_signed_attestation(
1126            &root_kp,
1127            &device_kp,
1128            &root_did,
1129            &device_did_str,
1130            None,
1131            Some(fixed_now() - Duration::days(1)),
1132        ));
1133        let att_revoked = verified(create_signed_attestation(
1134            &root_kp,
1135            &device_kp,
1136            &root_did,
1137            &device_did_str,
1138            Some(fixed_now()),
1139            Some(fixed_now() + Duration::days(365)),
1140        ));
1141        let att_valid = verified(create_signed_attestation(
1142            &root_kp,
1143            &device_kp,
1144            &root_did,
1145            &device_did_str,
1146            None,
1147            Some(fixed_now() + Duration::days(365)),
1148        ));
1149
1150        assert!(is_device_listed(
1151            &root_did,
1152            &device_did,
1153            &[att_expired, att_revoked, att_valid],
1154            fixed_now()
1155        ));
1156    }
1157
1158    #[test]
1159    fn is_device_listed_returns_false_for_wrong_identity() {
1160        let (_, root_pk) = create_test_keypair(&[1u8; 32]);
1161        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1162        let (other_kp, other_pk) = create_test_keypair(&[3u8; 32]);
1163        let other_did = auths_crypto::ed25519_pubkey_to_did_key(&other_pk);
1164        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1165        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1166        let device_did = DeviceDID::new(&device_did_str);
1167
1168        let att = create_signed_attestation(
1169            &other_kp,
1170            &device_kp,
1171            &other_did,
1172            &device_did_str,
1173            None,
1174            Some(fixed_now() + Duration::days(365)),
1175        );
1176        assert!(!is_device_listed(
1177            &root_did,
1178            &device_did,
1179            &[verified(att)],
1180            fixed_now()
1181        ));
1182    }
1183
1184    #[test]
1185    fn is_device_listed_returns_false_for_wrong_device() {
1186        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1187        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1188        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1189        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1190        let (_, other_device_pk) = create_test_keypair(&[4u8; 32]);
1191        let other_device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&other_device_pk);
1192        let other_device_did = DeviceDID::new(&other_device_did_str);
1193
1194        let att = create_signed_attestation(
1195            &root_kp,
1196            &device_kp,
1197            &root_did,
1198            &device_did_str,
1199            None,
1200            Some(fixed_now() + Duration::days(365)),
1201        );
1202        assert!(!is_device_listed(
1203            &root_did,
1204            &other_device_did,
1205            &[verified(att)],
1206            fixed_now()
1207        ));
1208    }
1209
1210    /// Helper to create a signed attestation with org fields
1211    fn create_signed_attestation_with_org_fields(
1212        issuer_kp: &Ed25519KeyPair,
1213        device_kp: &Ed25519KeyPair,
1214        issuer_did: &str,
1215        subject_did: &str,
1216        role: Option<Role>,
1217        capabilities: Vec<Capability>,
1218    ) -> Attestation {
1219        let device_pk: [u8; 32] = device_kp.public_key().as_ref().try_into().unwrap();
1220
1221        let mut att = Attestation {
1222            version: 1,
1223            rid: ResourceId::new("test-rid"),
1224            issuer: IdentityDID::new(issuer_did),
1225            subject: DeviceDID::new(subject_did),
1226            device_public_key: Ed25519PublicKey::from_bytes(device_pk),
1227            identity_signature: Ed25519Signature::empty(),
1228            device_signature: Ed25519Signature::empty(),
1229            revoked_at: None,
1230            expires_at: Some(fixed_now() + Duration::days(365)),
1231            timestamp: Some(fixed_now()),
1232            note: None,
1233            payload: None,
1234            role,
1235            capabilities: capabilities.clone(),
1236            delegated_by: None,
1237            signer_type: None,
1238        };
1239
1240        let caps_ref = if att.capabilities.is_empty() {
1241            None
1242        } else {
1243            Some(&att.capabilities)
1244        };
1245        let data = CanonicalAttestationData {
1246            version: att.version,
1247            rid: &att.rid,
1248            issuer: &att.issuer,
1249            subject: &att.subject,
1250            device_public_key: att.device_public_key.as_bytes(),
1251            payload: &att.payload,
1252            timestamp: &att.timestamp,
1253            expires_at: &att.expires_at,
1254            revoked_at: &att.revoked_at,
1255            note: &att.note,
1256            role: att.role.as_ref().map(|r| r.as_str()),
1257            capabilities: caps_ref,
1258            delegated_by: att.delegated_by.as_ref(),
1259            signer_type: att.signer_type.as_ref(),
1260        };
1261        let canonical_bytes = canonicalize_attestation_data(&data).unwrap();
1262
1263        att.identity_signature =
1264            Ed25519Signature::try_from_slice(issuer_kp.sign(&canonical_bytes).as_ref()).unwrap();
1265        att.device_signature =
1266            Ed25519Signature::try_from_slice(device_kp.sign(&canonical_bytes).as_ref()).unwrap();
1267
1268        att
1269    }
1270
1271    fn create_signed_attestation_with_caps(
1272        issuer_kp: &Ed25519KeyPair,
1273        device_kp: &Ed25519KeyPair,
1274        issuer_did: &str,
1275        subject_did: &str,
1276        capabilities: Vec<Capability>,
1277    ) -> Attestation {
1278        create_signed_attestation_with_org_fields(
1279            issuer_kp,
1280            device_kp,
1281            issuer_did,
1282            subject_did,
1283            None,
1284            capabilities,
1285        )
1286    }
1287
1288    #[tokio::test]
1289    async fn verify_with_capability_succeeds_when_capability_present() {
1290        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1291        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1292        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1293        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1294
1295        let att = create_signed_attestation_with_caps(
1296            &root_kp,
1297            &device_kp,
1298            &root_did,
1299            &device_did,
1300            vec![Capability::sign_commit(), Capability::sign_release()],
1301        );
1302
1303        let result = test_verifier()
1304            .verify_with_capability(&att, &Capability::sign_commit(), &root_pk)
1305            .await;
1306        assert!(result.is_ok());
1307    }
1308
1309    #[tokio::test]
1310    async fn verify_with_capability_fails_when_capability_missing() {
1311        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1312        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1313        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1314        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1315
1316        let att = create_signed_attestation_with_caps(
1317            &root_kp,
1318            &device_kp,
1319            &root_did,
1320            &device_did,
1321            vec![Capability::sign_commit()],
1322        );
1323
1324        let result = test_verifier()
1325            .verify_with_capability(&att, &Capability::manage_members(), &root_pk)
1326            .await;
1327        assert!(result.is_err());
1328        match result {
1329            Err(AttestationError::MissingCapability {
1330                required,
1331                available,
1332            }) => {
1333                assert_eq!(required, Capability::manage_members());
1334                assert_eq!(available, vec![Capability::sign_commit()]);
1335            }
1336            _ => panic!("Expected MissingCapability error"),
1337        }
1338    }
1339
1340    #[tokio::test]
1341    async fn verify_with_capability_fails_for_invalid_signature() {
1342        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1343        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1344        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1345        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1346
1347        let mut att = create_signed_attestation_with_caps(
1348            &root_kp,
1349            &device_kp,
1350            &root_did,
1351            &device_did,
1352            vec![Capability::sign_commit()],
1353        );
1354        let mut tampered = *att.identity_signature.as_bytes();
1355        tampered[0] ^= 0xFF;
1356        att.identity_signature = Ed25519Signature::from_bytes(tampered);
1357
1358        let result = test_verifier()
1359            .verify_with_capability(&att, &Capability::sign_commit(), &root_pk)
1360            .await;
1361        assert!(result.is_err());
1362        match result {
1363            Err(AttestationError::VerificationError(_)) => {}
1364            _ => panic!("Expected VerificationError, got {:?}", result),
1365        }
1366    }
1367
1368    #[tokio::test]
1369    async fn verify_chain_with_capability_succeeds_for_single_link() {
1370        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1371        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1372        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1373        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1374
1375        let att = create_signed_attestation_with_caps(
1376            &root_kp,
1377            &device_kp,
1378            &root_did,
1379            &device_did,
1380            vec![Capability::sign_commit(), Capability::sign_release()],
1381        );
1382
1383        let result = test_verifier()
1384            .verify_chain_with_capability(&[att], &Capability::sign_commit(), &root_pk)
1385            .await;
1386        assert!(result.is_ok());
1387        assert!(result.unwrap().is_valid());
1388    }
1389
1390    #[tokio::test]
1391    async fn verify_chain_with_capability_uses_intersection() {
1392        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1393        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1394        let (identity_kp, identity_pk) = create_test_keypair(&[2u8; 32]);
1395        let identity_did = auths_crypto::ed25519_pubkey_to_did_key(&identity_pk);
1396        let (device_kp, device_pk) = create_test_keypair(&[3u8; 32]);
1397        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1398
1399        let att1 = create_signed_attestation_with_org_fields(
1400            &root_kp,
1401            &identity_kp,
1402            &root_did,
1403            &identity_did,
1404            None,
1405            vec![Capability::sign_commit(), Capability::manage_members()],
1406        );
1407        let att2 = create_signed_attestation_with_org_fields(
1408            &identity_kp,
1409            &device_kp,
1410            &identity_did,
1411            &device_did,
1412            None,
1413            vec![Capability::sign_commit(), Capability::sign_release()],
1414        );
1415
1416        let result = test_verifier()
1417            .verify_chain_with_capability(
1418                &[att1.clone(), att2.clone()],
1419                &Capability::sign_commit(),
1420                &root_pk,
1421            )
1422            .await;
1423        assert!(result.is_ok());
1424
1425        let result = test_verifier()
1426            .verify_chain_with_capability(&[att1, att2], &Capability::manage_members(), &root_pk)
1427            .await;
1428        assert!(result.is_err());
1429        match result {
1430            Err(AttestationError::MissingCapability { required, .. }) => {
1431                assert_eq!(required, Capability::manage_members());
1432            }
1433            _ => panic!("Expected MissingCapability error"),
1434        }
1435    }
1436
1437    #[tokio::test]
1438    async fn verify_chain_with_capability_returns_report_on_invalid_chain() {
1439        let result = test_verifier()
1440            .verify_chain_with_capability(&[], &Capability::sign_commit(), &[0u8; 32])
1441            .await;
1442        assert!(result.is_ok());
1443        let report = result.unwrap();
1444        assert!(!report.is_valid());
1445    }
1446
1447    #[tokio::test]
1448    async fn verify_attestation_rejects_tampered_role() {
1449        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1450        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1451        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1452        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1453
1454        let mut att = create_signed_attestation_with_org_fields(
1455            &root_kp,
1456            &device_kp,
1457            &root_did,
1458            &device_did,
1459            Some(Role::Member),
1460            vec![Capability::sign_commit()],
1461        );
1462
1463        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1464        assert!(result.is_ok(), "Attestation should verify before tampering");
1465
1466        att.role = Some(Role::Admin);
1467        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1468        assert!(result.is_err(), "Attestation should reject tampered role");
1469        let err_msg = result.unwrap_err().to_string();
1470        assert!(
1471            err_msg.contains("signature"),
1472            "Error should mention signature failure: {}",
1473            err_msg
1474        );
1475    }
1476
1477    #[tokio::test]
1478    async fn verify_attestation_rejects_tampered_capabilities() {
1479        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1480        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1481        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1482        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1483
1484        let mut att = create_signed_attestation_with_org_fields(
1485            &root_kp,
1486            &device_kp,
1487            &root_did,
1488            &device_did,
1489            Some(Role::Member),
1490            vec![Capability::sign_commit()],
1491        );
1492        assert!(
1493            test_verifier()
1494                .verify_with_keys(&att, &root_pk)
1495                .await
1496                .is_ok()
1497        );
1498
1499        att.capabilities.push(Capability::manage_members());
1500        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1501        assert!(
1502            result.is_err(),
1503            "Attestation should reject tampered capabilities"
1504        );
1505    }
1506
1507    #[tokio::test]
1508    async fn verify_attestation_rejects_tampered_delegated_by() {
1509        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1510        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1511        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1512        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1513
1514        let mut att = create_signed_attestation_with_org_fields(
1515            &root_kp,
1516            &device_kp,
1517            &root_did,
1518            &device_did,
1519            Some(Role::Member),
1520            vec![Capability::sign_commit()],
1521        );
1522        assert!(
1523            test_verifier()
1524                .verify_with_keys(&att, &root_pk)
1525                .await
1526                .is_ok()
1527        );
1528
1529        att.delegated_by = Some(IdentityDID::new("did:key:attacker"));
1530        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1531        assert!(
1532            result.is_err(),
1533            "Attestation should reject tampered delegated_by"
1534        );
1535    }
1536
1537    #[tokio::test]
1538    async fn verify_attestation_valid_with_org_fields() {
1539        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1540        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1541        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1542        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1543
1544        let att = create_signed_attestation_with_org_fields(
1545            &root_kp,
1546            &device_kp,
1547            &root_did,
1548            &device_did,
1549            Some(Role::Admin),
1550            vec![Capability::sign_commit(), Capability::manage_members()],
1551        );
1552
1553        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1554        assert!(
1555            result.is_ok(),
1556            "Attestation with org fields should verify: {:?}",
1557            result.err()
1558        );
1559    }
1560
1561    fn create_signed_attestation_with_timestamp(
1562        issuer_kp: &Ed25519KeyPair,
1563        device_kp: &Ed25519KeyPair,
1564        issuer_did: &str,
1565        subject_did: &str,
1566        timestamp: Option<DateTime<Utc>>,
1567    ) -> Attestation {
1568        let device_pk: [u8; 32] = device_kp.public_key().as_ref().try_into().unwrap();
1569
1570        let mut att = Attestation {
1571            version: 1,
1572            rid: ResourceId::new("test-rid"),
1573            issuer: IdentityDID::new(issuer_did),
1574            subject: DeviceDID::new(subject_did),
1575            device_public_key: Ed25519PublicKey::from_bytes(device_pk),
1576            identity_signature: Ed25519Signature::empty(),
1577            device_signature: Ed25519Signature::empty(),
1578            revoked_at: None,
1579            expires_at: Some(fixed_now() + Duration::days(365)),
1580            timestamp,
1581            note: None,
1582            payload: None,
1583            role: None,
1584            capabilities: vec![],
1585            delegated_by: None,
1586            signer_type: None,
1587        };
1588
1589        let data = CanonicalAttestationData {
1590            version: att.version,
1591            rid: &att.rid,
1592            issuer: &att.issuer,
1593            subject: &att.subject,
1594            device_public_key: att.device_public_key.as_bytes(),
1595            payload: &att.payload,
1596            timestamp: &att.timestamp,
1597            expires_at: &att.expires_at,
1598            revoked_at: &att.revoked_at,
1599            note: &att.note,
1600            role: att.role.as_ref().map(|r| r.as_str()),
1601            capabilities: if att.capabilities.is_empty() {
1602                None
1603            } else {
1604                Some(&att.capabilities)
1605            },
1606            delegated_by: att.delegated_by.as_ref(),
1607            signer_type: att.signer_type.as_ref(),
1608        };
1609        let canonical_bytes = canonicalize_attestation_data(&data).unwrap();
1610
1611        att.identity_signature =
1612            Ed25519Signature::try_from_slice(issuer_kp.sign(&canonical_bytes).as_ref()).unwrap();
1613        att.device_signature =
1614            Ed25519Signature::try_from_slice(device_kp.sign(&canonical_bytes).as_ref()).unwrap();
1615
1616        att
1617    }
1618
1619    #[tokio::test]
1620    async fn verify_attestation_created_1_hour_ago_succeeds() {
1621        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1622        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1623        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1624        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1625
1626        let one_hour_ago = fixed_now() - Duration::hours(1);
1627        let att = create_signed_attestation_with_timestamp(
1628            &root_kp,
1629            &device_kp,
1630            &root_did,
1631            &device_did,
1632            Some(one_hour_ago),
1633        );
1634
1635        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1636        assert!(
1637            result.is_ok(),
1638            "Attestation created 1 hour ago should verify: {:?}",
1639            result.err()
1640        );
1641    }
1642
1643    #[tokio::test]
1644    async fn verify_attestation_created_30_days_ago_succeeds() {
1645        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1646        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1647        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1648        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1649
1650        let thirty_days_ago = fixed_now() - Duration::days(30);
1651        let att = create_signed_attestation_with_timestamp(
1652            &root_kp,
1653            &device_kp,
1654            &root_did,
1655            &device_did,
1656            Some(thirty_days_ago),
1657        );
1658
1659        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1660        assert!(
1661            result.is_ok(),
1662            "Attestation created 30 days ago should verify: {:?}",
1663            result.err()
1664        );
1665    }
1666
1667    #[tokio::test]
1668    async fn verify_attestation_with_future_timestamp_fails() {
1669        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1670        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1671        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1672        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1673
1674        let ten_minutes_future = fixed_now() + Duration::minutes(10);
1675        let att = create_signed_attestation_with_timestamp(
1676            &root_kp,
1677            &device_kp,
1678            &root_did,
1679            &device_did,
1680            Some(ten_minutes_future),
1681        );
1682
1683        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1684        assert!(
1685            result.is_err(),
1686            "Attestation with future timestamp should fail"
1687        );
1688        let err_msg = result.unwrap_err().to_string();
1689        assert!(
1690            err_msg.contains("in the future"),
1691            "Error should mention 'in the future': {}",
1692            err_msg
1693        );
1694    }
1695
1696    #[tokio::test]
1697    async fn verify_device_authorization_returns_valid_for_signed_attestation() {
1698        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1699        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1700        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1701        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1702        let device_did = DeviceDID::new(&device_did_str);
1703
1704        let att = create_signed_attestation(
1705            &root_kp,
1706            &device_kp,
1707            &root_did,
1708            &device_did_str,
1709            None,
1710            Some(fixed_now() + Duration::days(365)),
1711        );
1712
1713        let result = test_verifier()
1714            .verify_device_authorization(&root_did, &device_did, &[att], &root_pk)
1715            .await;
1716        assert!(result.is_ok());
1717        let report = result.unwrap();
1718        assert!(report.is_valid());
1719        assert_eq!(report.chain.len(), 1);
1720        assert!(report.chain[0].valid);
1721    }
1722
1723    #[tokio::test]
1724    async fn verify_device_authorization_returns_broken_chain_for_no_attestations() {
1725        let (_, root_pk) = create_test_keypair(&[1u8; 32]);
1726        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1727        let (_, device_pk) = create_test_keypair(&[2u8; 32]);
1728        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1729        let device_did = DeviceDID::new(&device_did_str);
1730
1731        let result = test_verifier()
1732            .verify_device_authorization(&root_did, &device_did, &[], &root_pk)
1733            .await;
1734        assert!(result.is_ok());
1735        let report = result.unwrap();
1736        assert!(!report.is_valid());
1737        match report.status {
1738            VerificationStatus::BrokenChain { missing_link } => {
1739                assert!(missing_link.contains("No attestation found"));
1740            }
1741            _ => panic!("Expected BrokenChain status, got {:?}", report.status),
1742        }
1743    }
1744
1745    #[tokio::test]
1746    async fn verify_device_authorization_fails_for_forged_signature() {
1747        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1748        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1749        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1750        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1751        let device_did = DeviceDID::new(&device_did_str);
1752
1753        let mut att = create_signed_attestation(
1754            &root_kp,
1755            &device_kp,
1756            &root_did,
1757            &device_did_str,
1758            None,
1759            Some(fixed_now() + Duration::days(365)),
1760        );
1761        let mut tampered = *att.identity_signature.as_bytes();
1762        tampered[0] ^= 0xFF;
1763        att.identity_signature = Ed25519Signature::from_bytes(tampered);
1764
1765        let result = test_verifier()
1766            .verify_device_authorization(&root_did, &device_did, &[att], &root_pk)
1767            .await;
1768        assert!(result.is_ok());
1769        let report = result.unwrap();
1770        assert!(!report.is_valid());
1771        match report.status {
1772            VerificationStatus::InvalidSignature { step } => assert_eq!(step, 0),
1773            _ => panic!("Expected InvalidSignature status, got {:?}", report.status),
1774        }
1775    }
1776
1777    #[tokio::test]
1778    async fn verify_device_authorization_fails_for_wrong_issuer_key() {
1779        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1780        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1781        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1782        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1783        let device_did = DeviceDID::new(&device_did_str);
1784        let (_, wrong_pk) = create_test_keypair(&[99u8; 32]);
1785
1786        let att = create_signed_attestation(
1787            &root_kp,
1788            &device_kp,
1789            &root_did,
1790            &device_did_str,
1791            None,
1792            Some(fixed_now() + Duration::days(365)),
1793        );
1794
1795        let result = test_verifier()
1796            .verify_device_authorization(&root_did, &device_did, &[att], &wrong_pk)
1797            .await;
1798        assert!(result.is_ok());
1799        let report = result.unwrap();
1800        assert!(!report.is_valid());
1801        match report.status {
1802            VerificationStatus::InvalidSignature { .. } => {}
1803            _ => panic!("Expected InvalidSignature status, got {:?}", report.status),
1804        }
1805    }
1806
1807    #[tokio::test]
1808    async fn verify_device_authorization_checks_expiry_and_revocation() {
1809        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1810        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1811        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1812        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1813        let device_did = DeviceDID::new(&device_did_str);
1814
1815        let att_expired = create_signed_attestation(
1816            &root_kp,
1817            &device_kp,
1818            &root_did,
1819            &device_did_str,
1820            None,
1821            Some(fixed_now() - Duration::days(1)),
1822        );
1823        let result = test_verifier()
1824            .verify_device_authorization(&root_did, &device_did, &[att_expired], &root_pk)
1825            .await;
1826        assert!(result.is_ok());
1827        let report = result.unwrap();
1828        assert!(!report.is_valid());
1829        match report.status {
1830            VerificationStatus::Expired { .. } => {}
1831            _ => panic!("Expected Expired status, got {:?}", report.status),
1832        }
1833
1834        let att_revoked = create_signed_attestation(
1835            &root_kp,
1836            &device_kp,
1837            &root_did,
1838            &device_did_str,
1839            Some(fixed_now()),
1840            Some(fixed_now() + Duration::days(365)),
1841        );
1842        let result = test_verifier()
1843            .verify_device_authorization(&root_did, &device_did, &[att_revoked], &root_pk)
1844            .await;
1845        assert!(result.is_ok());
1846        let report = result.unwrap();
1847        assert!(!report.is_valid());
1848        match report.status {
1849            VerificationStatus::Revoked { .. } => {}
1850            _ => panic!("Expected Revoked status, got {:?}", report.status),
1851        }
1852    }
1853
1854    fn create_witness_receipt(
1855        witness_kp: &Ed25519KeyPair,
1856        witness_did: &str,
1857        event_said: &str,
1858        seq: u64,
1859    ) -> crate::witness::WitnessReceipt {
1860        let mut receipt = crate::witness::WitnessReceipt {
1861            v: "KERI10JSON000000_".into(),
1862            t: "rct".into(),
1863            d: Said::new_unchecked(format!("EReceipt_{}", seq)),
1864            i: witness_did.into(),
1865            s: seq,
1866            a: Said::new_unchecked(event_said.to_string()),
1867            sig: vec![],
1868        };
1869        let payload = receipt.signing_payload().unwrap();
1870        receipt.sig = witness_kp.sign(&payload).as_ref().to_vec();
1871        receipt
1872    }
1873
1874    #[tokio::test]
1875    async fn verify_chain_with_witnesses_valid() {
1876        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1877        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1878        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1879        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1880
1881        let att = create_signed_attestation(
1882            &root_kp,
1883            &device_kp,
1884            &root_did,
1885            &device_did,
1886            None,
1887            Some(fixed_now() + Duration::days(365)),
1888        );
1889
1890        let (w1_kp, w1_pk) = create_test_keypair(&[10u8; 32]);
1891        let (w2_kp, w2_pk) = create_test_keypair(&[20u8; 32]);
1892
1893        let r1 = create_witness_receipt(&w1_kp, "did:key:w1", "EEvent1", 1);
1894        let r2 = create_witness_receipt(&w2_kp, "did:key:w2", "EEvent1", 1);
1895
1896        let witness_keys = vec![
1897            ("did:key:w1".into(), w1_pk.to_vec()),
1898            ("did:key:w2".into(), w2_pk.to_vec()),
1899        ];
1900
1901        let config = crate::witness::WitnessVerifyConfig {
1902            receipts: &[r1, r2],
1903            witness_keys: &witness_keys,
1904            threshold: 2,
1905        };
1906
1907        let report = test_verifier()
1908            .verify_chain_with_witnesses(&[att], &root_pk, &config)
1909            .await
1910            .unwrap();
1911        assert!(report.is_valid());
1912        assert!(report.witness_quorum.is_some());
1913        let quorum = report.witness_quorum.unwrap();
1914        assert_eq!(quorum.required, 2);
1915        assert_eq!(quorum.verified, 2);
1916    }
1917
1918    #[tokio::test]
1919    async fn verify_chain_with_witnesses_chain_fails() {
1920        let (w1_kp, w1_pk) = create_test_keypair(&[10u8; 32]);
1921        let r1 = create_witness_receipt(&w1_kp, "did:key:w1", "EEvent1", 1);
1922        let witness_keys = vec![("did:key:w1".into(), w1_pk.to_vec())];
1923        let config = crate::witness::WitnessVerifyConfig {
1924            receipts: &[r1],
1925            witness_keys: &witness_keys,
1926            threshold: 1,
1927        };
1928
1929        let report = test_verifier()
1930            .verify_chain_with_witnesses(&[], &[0u8; 32], &config)
1931            .await
1932            .unwrap();
1933        assert!(!report.is_valid());
1934        match report.status {
1935            VerificationStatus::BrokenChain { .. } => {}
1936            _ => panic!("Expected BrokenChain, got {:?}", report.status),
1937        }
1938        assert!(report.witness_quorum.is_none());
1939    }
1940
1941    #[tokio::test]
1942    async fn verify_chain_with_witnesses_quorum_fails() {
1943        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1944        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1945        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1946        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1947
1948        let att = create_signed_attestation(
1949            &root_kp,
1950            &device_kp,
1951            &root_did,
1952            &device_did,
1953            None,
1954            Some(fixed_now() + Duration::days(365)),
1955        );
1956
1957        let (w1_kp, w1_pk) = create_test_keypair(&[10u8; 32]);
1958        let r1 = create_witness_receipt(&w1_kp, "did:key:w1", "EEvent1", 1);
1959        let witness_keys = vec![("did:key:w1".into(), w1_pk.to_vec())];
1960        let config = crate::witness::WitnessVerifyConfig {
1961            receipts: &[r1],
1962            witness_keys: &witness_keys,
1963            threshold: 2,
1964        };
1965
1966        let report = test_verifier()
1967            .verify_chain_with_witnesses(&[att], &root_pk, &config)
1968            .await
1969            .unwrap();
1970        assert!(!report.is_valid());
1971        match report.status {
1972            VerificationStatus::InsufficientWitnesses { required, verified } => {
1973                assert_eq!(required, 2);
1974                assert_eq!(verified, 1);
1975            }
1976            _ => panic!("Expected InsufficientWitnesses, got {:?}", report.status),
1977        }
1978        assert!(report.witness_quorum.is_some());
1979        assert!(!report.warnings.is_empty());
1980    }
1981}