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::AttestationRevoked);
337    }
338
339    // --- 2. Check expiration against reference time ---
340    if let Some(exp) = att.expires_at
341        && reference_time > exp
342    {
343        return Err(AttestationError::AttestationExpired {
344            at: exp.to_rfc3339(),
345        });
346    }
347
348    // --- 3. Check timestamp skew against reference time ---
349    if check_skew
350        && let Some(ts) = att.timestamp
351        && ts > reference_time + Duration::seconds(MAX_SKEW_SECS)
352    {
353        return Err(AttestationError::TimestampInFuture {
354            at: ts.to_rfc3339(),
355        });
356    }
357
358    // --- 4. Check provided issuer public key length ---
359    if !att.identity_signature.is_empty() && issuer_pk_bytes.len() != ED25519_PUBLIC_KEY_LEN {
360        return Err(AttestationError::InvalidInput(format!(
361            "Provided issuer public key has invalid length: {}",
362            issuer_pk_bytes.len()
363        )));
364    }
365
366    // --- 5. Reconstruct and canonicalize data ---
367    let data_to_canonicalize = CanonicalAttestationData {
368        version: att.version,
369        rid: &att.rid,
370        issuer: &att.issuer,
371        subject: &att.subject,
372        device_public_key: att.device_public_key.as_bytes(),
373        payload: &att.payload,
374        timestamp: &att.timestamp,
375        expires_at: &att.expires_at,
376        revoked_at: &att.revoked_at,
377        note: &att.note,
378        role: att.role.as_ref().map(|r| r.as_str()),
379        capabilities: if att.capabilities.is_empty() {
380            None
381        } else {
382            Some(&att.capabilities)
383        },
384        delegated_by: att.delegated_by.as_ref(),
385        signer_type: att.signer_type.as_ref(),
386    };
387    let canonical_json_bytes = canonicalize_attestation_data(&data_to_canonicalize)?;
388    let data_to_verify = canonical_json_bytes.as_slice();
389    debug!(
390        "(Verify) Canonical data: {}",
391        String::from_utf8_lossy(&canonical_json_bytes)
392    );
393
394    // --- 6. Verify issuer signature ---
395    if !att.identity_signature.is_empty() {
396        provider
397            .verify_ed25519(
398                issuer_pk_bytes,
399                data_to_verify,
400                att.identity_signature.as_bytes(),
401            )
402            .await
403            .map_err(|e| AttestationError::IssuerSignatureFailed(e.to_string()))?;
404        debug!("(Verify) Issuer signature verified successfully.");
405    } else {
406        debug!(
407            "(Verify) No identity signature present (device-only attestation), skipping issuer check."
408        );
409    }
410
411    // --- 7. Verify device signature ---
412    provider
413        .verify_ed25519(
414            att.device_public_key.as_bytes(),
415            data_to_verify,
416            att.device_signature.as_bytes(),
417        )
418        .await
419        .map_err(|e| AttestationError::DeviceSignatureFailed(e.to_string()))?;
420    debug!("(Verify) Device signature verified successfully.");
421
422    Ok(())
423}
424
425pub(crate) async fn verify_chain_inner(
426    attestations: &[Attestation],
427    root_pk: &[u8],
428    provider: &dyn CryptoProvider,
429    now: DateTime<Utc>,
430) -> Result<VerificationReport, AttestationError> {
431    if attestations.is_empty() {
432        return Ok(VerificationReport::with_status(
433            VerificationStatus::BrokenChain {
434                missing_link: "empty chain".to_string(),
435            },
436            vec![],
437        ));
438    }
439
440    let mut chain_links: Vec<ChainLink> = Vec::with_capacity(attestations.len());
441
442    let first_att = &attestations[0];
443    match verify_single_attestation(first_att, root_pk, 0, provider, now).await {
444        Ok(link) => chain_links.push(link),
445        Err((status, link)) => {
446            chain_links.push(link);
447            return Ok(VerificationReport::with_status(status, chain_links));
448        }
449    }
450
451    for (idx, att) in attestations.iter().enumerate().skip(1) {
452        let prev_att = &attestations[idx - 1];
453
454        if att.issuer.as_str() != prev_att.subject.as_str() {
455            let link = ChainLink::invalid(
456                att.issuer.to_string(),
457                att.subject.to_string(),
458                format!(
459                    "Chain broken: expected issuer '{}', got '{}'",
460                    prev_att.subject, att.issuer
461                ),
462            );
463            chain_links.push(link);
464            return Ok(VerificationReport::with_status(
465                VerificationStatus::BrokenChain {
466                    missing_link: format!(
467                        "Issuer mismatch at step {}: expected '{}', got '{}'",
468                        idx, prev_att.subject, att.issuer
469                    ),
470                },
471                chain_links,
472            ));
473        }
474
475        let issuer_pk = prev_att.device_public_key.as_bytes().as_slice();
476
477        match verify_single_attestation(att, issuer_pk, idx, provider, now).await {
478            Ok(link) => chain_links.push(link),
479            Err((status, link)) => {
480                chain_links.push(link);
481                return Ok(VerificationReport::with_status(status, chain_links));
482            }
483        }
484    }
485
486    Ok(VerificationReport::valid(chain_links))
487}
488
489pub(crate) async fn verify_device_authorization_inner(
490    identity_did: &str,
491    device_did: &DeviceDID,
492    attestations: &[Attestation],
493    identity_pk: &[u8],
494    provider: &dyn CryptoProvider,
495    now: DateTime<Utc>,
496) -> Result<VerificationReport, AttestationError> {
497    let device_did_str = device_did.to_string();
498
499    let matching: Vec<&Attestation> = attestations
500        .iter()
501        .filter(|a| a.issuer == identity_did && a.subject.to_string() == device_did_str)
502        .collect();
503
504    if matching.is_empty() {
505        return Ok(VerificationReport::with_status(
506            VerificationStatus::BrokenChain {
507                missing_link: format!(
508                    "No attestation found for device {} under {}",
509                    device_did_str, identity_did
510                ),
511            },
512            vec![],
513        ));
514    }
515
516    match verify_single_attestation(matching[0], identity_pk, 0, provider, now).await {
517        Ok(link) => Ok(VerificationReport::valid(vec![link])),
518        Err((status, link)) => Ok(VerificationReport::with_status(status, vec![link])),
519    }
520}
521
522async fn verify_single_attestation(
523    att: &Attestation,
524    issuer_pk: &[u8],
525    step: usize,
526    provider: &dyn CryptoProvider,
527    now: DateTime<Utc>,
528) -> Result<ChainLink, (VerificationStatus, ChainLink)> {
529    let issuer = att.issuer.to_string();
530    let subject = att.subject.to_string();
531
532    if att.is_revoked() {
533        return Err((
534            VerificationStatus::Revoked { at: att.revoked_at },
535            ChainLink::invalid(issuer, subject, "Attestation revoked".to_string()),
536        ));
537    }
538
539    if let Some(exp) = att.expires_at
540        && now > exp
541    {
542        return Err((
543            VerificationStatus::Expired { at: exp },
544            ChainLink::invalid(
545                issuer,
546                subject,
547                format!("Attestation expired on {}", exp.to_rfc3339()),
548            ),
549        ));
550    }
551
552    match verify_with_keys_at(att, issuer_pk, now, true, provider).await {
553        Ok(()) => Ok(ChainLink::valid(issuer, subject)),
554        Err(e) => Err((
555            VerificationStatus::InvalidSignature { step },
556            ChainLink::invalid(issuer, subject, e.to_string()),
557        )),
558    }
559}
560
561#[cfg(all(test, not(target_arch = "wasm32")))]
562#[allow(clippy::unwrap_used, clippy::expect_used)]
563mod tests {
564    use super::*;
565    use crate::clock::ClockProvider;
566    use crate::core::{Capability, Ed25519PublicKey, Ed25519Signature, ResourceId, Role};
567    use crate::keri::Said;
568    use crate::types::{DeviceDID, IdentityDID};
569    use crate::verifier::Verifier;
570    use auths_crypto::RingCryptoProvider;
571    use auths_crypto::testing::create_test_keypair;
572    use chrono::{DateTime, Duration, TimeZone, Utc};
573    use ring::signature::{Ed25519KeyPair, KeyPair};
574    use std::sync::Arc;
575
576    struct TestClock(DateTime<Utc>);
577    impl ClockProvider for TestClock {
578        fn now(&self) -> DateTime<Utc> {
579            self.0
580        }
581    }
582
583    fn fixed_now() -> DateTime<Utc> {
584        Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap()
585    }
586
587    fn test_verifier() -> Verifier {
588        Verifier::new(
589            Arc::new(RingCryptoProvider),
590            Arc::new(TestClock(fixed_now())),
591        )
592    }
593
594    /// Helper to create a signed attestation
595    fn create_signed_attestation(
596        issuer_kp: &Ed25519KeyPair,
597        device_kp: &Ed25519KeyPair,
598        issuer_did: &str,
599        subject_did: &str,
600        revoked_at: Option<DateTime<Utc>>,
601        expires_at: Option<DateTime<Utc>>,
602    ) -> Attestation {
603        let device_pk: [u8; 32] = device_kp.public_key().as_ref().try_into().unwrap();
604
605        let mut att = Attestation {
606            version: 1,
607            rid: ResourceId::new("test-rid"),
608            issuer: IdentityDID::new(issuer_did),
609            subject: DeviceDID::new(subject_did),
610            device_public_key: Ed25519PublicKey::from_bytes(device_pk),
611            identity_signature: Ed25519Signature::empty(),
612            device_signature: Ed25519Signature::empty(),
613            revoked_at,
614            expires_at,
615            timestamp: Some(fixed_now()),
616            note: None,
617            payload: None,
618            role: None,
619            capabilities: vec![],
620            delegated_by: None,
621            signer_type: None,
622        };
623
624        let data = CanonicalAttestationData {
625            version: att.version,
626            rid: &att.rid,
627            issuer: &att.issuer,
628            subject: &att.subject,
629            device_public_key: att.device_public_key.as_bytes(),
630            payload: &att.payload,
631            timestamp: &att.timestamp,
632            expires_at: &att.expires_at,
633            revoked_at: &att.revoked_at,
634            note: &att.note,
635            role: att.role.as_ref().map(|r| r.as_str()),
636            capabilities: if att.capabilities.is_empty() {
637                None
638            } else {
639                Some(&att.capabilities)
640            },
641            delegated_by: att.delegated_by.as_ref(),
642            signer_type: att.signer_type.as_ref(),
643        };
644        let canonical_bytes = canonicalize_attestation_data(&data).unwrap();
645
646        att.identity_signature =
647            Ed25519Signature::try_from_slice(issuer_kp.sign(&canonical_bytes).as_ref()).unwrap();
648        att.device_signature =
649            Ed25519Signature::try_from_slice(device_kp.sign(&canonical_bytes).as_ref()).unwrap();
650
651        att
652    }
653
654    #[tokio::test]
655    async fn verify_chain_empty_returns_broken_chain() {
656        let result = test_verifier().verify_chain(&[], &[0u8; 32]).await.unwrap();
657        assert!(!result.is_valid());
658        match result.status {
659            VerificationStatus::BrokenChain { missing_link } => {
660                assert_eq!(missing_link, "empty chain");
661            }
662            _ => panic!("Expected BrokenChain status"),
663        }
664        assert!(result.chain.is_empty());
665    }
666
667    #[tokio::test]
668    async fn verify_chain_single_valid_attestation() {
669        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
670        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
671        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
672        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
673
674        let att = create_signed_attestation(
675            &root_kp,
676            &device_kp,
677            &root_did,
678            &device_did,
679            None,
680            Some(fixed_now() + Duration::days(365)),
681        );
682
683        let result = test_verifier()
684            .verify_chain(&[att], &root_pk)
685            .await
686            .unwrap();
687        assert!(result.is_valid());
688        assert_eq!(result.chain.len(), 1);
689        assert!(result.chain[0].valid);
690    }
691
692    #[tokio::test]
693    async fn verify_chain_revoked_attestation_returns_revoked() {
694        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
695        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
696        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
697        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
698
699        let att = create_signed_attestation(
700            &root_kp,
701            &device_kp,
702            &root_did,
703            &device_did,
704            Some(fixed_now()),
705            Some(fixed_now() + Duration::days(365)),
706        );
707
708        let result = test_verifier()
709            .verify_chain(&[att], &root_pk)
710            .await
711            .unwrap();
712        assert!(!result.is_valid());
713        match result.status {
714            VerificationStatus::Revoked { .. } => {}
715            _ => panic!("Expected Revoked status, got {:?}", result.status),
716        }
717    }
718
719    #[tokio::test]
720    async fn verify_chain_expired_attestation_returns_expired() {
721        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
722        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
723        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
724        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
725
726        let att = create_signed_attestation(
727            &root_kp,
728            &device_kp,
729            &root_did,
730            &device_did,
731            None,
732            Some(fixed_now() - Duration::days(1)),
733        );
734
735        let result = test_verifier()
736            .verify_chain(&[att], &root_pk)
737            .await
738            .unwrap();
739        assert!(!result.is_valid());
740        match result.status {
741            VerificationStatus::Expired { .. } => {}
742            _ => panic!("Expected Expired status, got {:?}", result.status),
743        }
744    }
745
746    #[tokio::test]
747    async fn verify_chain_invalid_signature_returns_invalid_signature() {
748        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
749        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
750        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
751        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
752
753        let mut att = create_signed_attestation(
754            &root_kp,
755            &device_kp,
756            &root_did,
757            &device_did,
758            None,
759            Some(fixed_now() + Duration::days(365)),
760        );
761        let mut tampered = *att.identity_signature.as_bytes();
762        tampered[0] ^= 0xFF;
763        att.identity_signature = Ed25519Signature::from_bytes(tampered);
764
765        let result = test_verifier()
766            .verify_chain(&[att], &root_pk)
767            .await
768            .unwrap();
769        assert!(!result.is_valid());
770        match result.status {
771            VerificationStatus::InvalidSignature { step } => {
772                assert_eq!(step, 0);
773            }
774            _ => panic!("Expected InvalidSignature status, got {:?}", result.status),
775        }
776    }
777
778    #[tokio::test]
779    async fn verify_chain_broken_link_returns_broken_chain() {
780        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
781        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
782        let (device1_kp, device1_pk) = create_test_keypair(&[2u8; 32]);
783        let device1_did = auths_crypto::ed25519_pubkey_to_did_key(&device1_pk);
784        let (device2_kp, device2_pk) = create_test_keypair(&[3u8; 32]);
785        let device2_did = auths_crypto::ed25519_pubkey_to_did_key(&device2_pk);
786
787        let att1 = create_signed_attestation(
788            &root_kp,
789            &device1_kp,
790            &root_did,
791            &device1_did,
792            None,
793            Some(fixed_now() + Duration::days(365)),
794        );
795        let att2 = create_signed_attestation(
796            &device1_kp,
797            &device2_kp,
798            &root_did, // WRONG: should be device1_did
799            &device2_did,
800            None,
801            Some(fixed_now() + Duration::days(365)),
802        );
803
804        let result = test_verifier()
805            .verify_chain(&[att1, att2], &root_pk)
806            .await
807            .unwrap();
808        assert!(!result.is_valid());
809        match result.status {
810            VerificationStatus::BrokenChain { missing_link } => {
811                assert!(missing_link.contains("Issuer mismatch"));
812            }
813            _ => panic!("Expected BrokenChain status, got {:?}", result.status),
814        }
815    }
816
817    #[tokio::test]
818    async fn verify_chain_valid_three_level_chain() {
819        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
820        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
821        let (identity_kp, identity_pk) = create_test_keypair(&[2u8; 32]);
822        let identity_did = auths_crypto::ed25519_pubkey_to_did_key(&identity_pk);
823        let (device_kp, device_pk) = create_test_keypair(&[3u8; 32]);
824        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
825
826        let att1 = create_signed_attestation(
827            &root_kp,
828            &identity_kp,
829            &root_did,
830            &identity_did,
831            None,
832            Some(fixed_now() + Duration::days(365)),
833        );
834        let att2 = create_signed_attestation(
835            &identity_kp,
836            &device_kp,
837            &identity_did,
838            &device_did,
839            None,
840            Some(fixed_now() + Duration::days(365)),
841        );
842
843        let result = test_verifier()
844            .verify_chain(&[att1, att2], &root_pk)
845            .await
846            .unwrap();
847        assert!(result.is_valid());
848        assert_eq!(result.chain.len(), 2);
849        assert!(result.chain[0].valid);
850        assert!(result.chain[1].valid);
851    }
852
853    #[tokio::test]
854    async fn verify_chain_revoked_intermediate_returns_revoked() {
855        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
856        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
857        let (identity_kp, identity_pk) = create_test_keypair(&[2u8; 32]);
858        let identity_did = auths_crypto::ed25519_pubkey_to_did_key(&identity_pk);
859        let (device_kp, device_pk) = create_test_keypair(&[3u8; 32]);
860        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
861
862        let att1 = create_signed_attestation(
863            &root_kp,
864            &identity_kp,
865            &root_did,
866            &identity_did,
867            None,
868            Some(fixed_now() + Duration::days(365)),
869        );
870        let att2 = create_signed_attestation(
871            &identity_kp,
872            &device_kp,
873            &identity_did,
874            &device_did,
875            Some(fixed_now()),
876            Some(fixed_now() + Duration::days(365)),
877        );
878
879        let result = test_verifier()
880            .verify_chain(&[att1, att2], &root_pk)
881            .await
882            .unwrap();
883        assert!(!result.is_valid());
884        match result.status {
885            VerificationStatus::Revoked { .. } => {}
886            _ => panic!("Expected Revoked status, got {:?}", result.status),
887        }
888        assert_eq!(result.chain.len(), 2);
889        assert!(result.chain[0].valid);
890        assert!(!result.chain[1].valid);
891    }
892
893    #[tokio::test]
894    async fn verify_at_time_valid_before_expiration() {
895        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
896        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
897        let (device_kp, _) = create_test_keypair(&[2u8; 32]);
898        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
899
900        let expires = fixed_now() + Duration::days(30);
901        let att = create_signed_attestation(
902            &root_kp,
903            &device_kp,
904            &root_did,
905            &device_did,
906            None,
907            Some(expires),
908        );
909
910        let verification_time = fixed_now() + Duration::days(10);
911        let result = test_verifier()
912            .verify_at_time(&att, &root_pk, verification_time)
913            .await;
914        assert!(result.is_ok());
915    }
916
917    #[tokio::test]
918    async fn verify_at_time_expired_after_expiration() {
919        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
920        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
921        let (device_kp, _) = create_test_keypair(&[2u8; 32]);
922        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
923
924        let expires = fixed_now() + Duration::days(30);
925        let att = create_signed_attestation(
926            &root_kp,
927            &device_kp,
928            &root_did,
929            &device_did,
930            None,
931            Some(expires),
932        );
933
934        let verification_time = fixed_now() + Duration::days(60);
935        let result = test_verifier()
936            .verify_at_time(&att, &root_pk, verification_time)
937            .await;
938        assert!(result.is_err());
939        assert!(result.unwrap_err().to_string().contains("expired"));
940    }
941
942    #[tokio::test]
943    async fn verify_at_time_signature_always_checked() {
944        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
945        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
946        let (device_kp, _) = create_test_keypair(&[2u8; 32]);
947        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
948
949        let mut att = create_signed_attestation(
950            &root_kp,
951            &device_kp,
952            &root_did,
953            &device_did,
954            None,
955            Some(fixed_now() + Duration::days(365)),
956        );
957        let mut tampered = *att.identity_signature.as_bytes();
958        tampered[0] ^= 0xFF;
959        att.identity_signature = Ed25519Signature::from_bytes(tampered);
960
961        let verification_time = fixed_now() - Duration::days(10);
962        let result = test_verifier()
963            .verify_at_time(&att, &root_pk, verification_time)
964            .await;
965        assert!(result.is_err());
966        assert!(
967            result
968                .unwrap_err()
969                .to_string()
970                .contains("signature verification failed")
971        );
972    }
973
974    #[tokio::test]
975    async fn verify_at_time_with_past_time_skips_skew_check() {
976        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
977        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
978        let (device_kp, _) = create_test_keypair(&[2u8; 32]);
979        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
980
981        let att = create_signed_attestation(
982            &root_kp,
983            &device_kp,
984            &root_did,
985            &device_did,
986            None,
987            Some(fixed_now() + Duration::days(365)),
988        );
989
990        let verification_time = fixed_now() - Duration::days(30);
991        let result = test_verifier()
992            .verify_at_time(&att, &root_pk, verification_time)
993            .await;
994        assert!(result.is_ok());
995    }
996
997    #[tokio::test]
998    async fn verify_with_keys_still_works() {
999        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1000        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1001        let (device_kp, _) = create_test_keypair(&[2u8; 32]);
1002        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1003
1004        let att = create_signed_attestation(
1005            &root_kp,
1006            &device_kp,
1007            &root_did,
1008            &device_did,
1009            None,
1010            Some(fixed_now() + Duration::days(365)),
1011        );
1012
1013        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1014        assert!(result.is_ok());
1015    }
1016
1017    /// Helper to wrap an attestation as verified (for tests where we created it ourselves).
1018    fn verified(att: Attestation) -> VerifiedAttestation {
1019        VerifiedAttestation::dangerous_from_unchecked(att)
1020    }
1021
1022    #[test]
1023    fn is_device_listed_returns_true_for_valid_attestation() {
1024        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1025        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1026        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1027        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1028        let device_did = DeviceDID::new(&device_did_str);
1029
1030        let att = create_signed_attestation(
1031            &root_kp,
1032            &device_kp,
1033            &root_did,
1034            &device_did_str,
1035            None,
1036            Some(fixed_now() + Duration::days(365)),
1037        );
1038
1039        assert!(is_device_listed(
1040            &root_did,
1041            &device_did,
1042            &[verified(att)],
1043            fixed_now()
1044        ));
1045    }
1046
1047    #[test]
1048    fn is_device_listed_returns_false_for_no_attestations() {
1049        let (_, root_pk) = create_test_keypair(&[1u8; 32]);
1050        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1051        let (_, device_pk) = create_test_keypair(&[2u8; 32]);
1052        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1053        let device_did = DeviceDID::new(&device_did_str);
1054
1055        assert!(!is_device_listed(&root_did, &device_did, &[], fixed_now()));
1056    }
1057
1058    #[test]
1059    fn is_device_listed_returns_false_for_expired_attestation() {
1060        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1061        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1062        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1063        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1064        let device_did = DeviceDID::new(&device_did_str);
1065
1066        let att = create_signed_attestation(
1067            &root_kp,
1068            &device_kp,
1069            &root_did,
1070            &device_did_str,
1071            None,
1072            Some(fixed_now() - Duration::days(1)),
1073        );
1074
1075        assert!(!is_device_listed(
1076            &root_did,
1077            &device_did,
1078            &[verified(att)],
1079            fixed_now()
1080        ));
1081    }
1082
1083    #[test]
1084    fn is_device_listed_returns_false_for_revoked_attestation() {
1085        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1086        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1087        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1088        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1089        let device_did = DeviceDID::new(&device_did_str);
1090
1091        let att = create_signed_attestation(
1092            &root_kp,
1093            &device_kp,
1094            &root_did,
1095            &device_did_str,
1096            Some(fixed_now()),
1097            Some(fixed_now() + Duration::days(365)),
1098        );
1099
1100        assert!(!is_device_listed(
1101            &root_did,
1102            &device_did,
1103            &[verified(att)],
1104            fixed_now()
1105        ));
1106    }
1107
1108    #[test]
1109    fn is_device_listed_returns_true_if_one_valid_among_many() {
1110        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1111        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1112        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1113        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1114        let device_did = DeviceDID::new(&device_did_str);
1115
1116        let att_expired = verified(create_signed_attestation(
1117            &root_kp,
1118            &device_kp,
1119            &root_did,
1120            &device_did_str,
1121            None,
1122            Some(fixed_now() - Duration::days(1)),
1123        ));
1124        let att_revoked = verified(create_signed_attestation(
1125            &root_kp,
1126            &device_kp,
1127            &root_did,
1128            &device_did_str,
1129            Some(fixed_now()),
1130            Some(fixed_now() + Duration::days(365)),
1131        ));
1132        let att_valid = verified(create_signed_attestation(
1133            &root_kp,
1134            &device_kp,
1135            &root_did,
1136            &device_did_str,
1137            None,
1138            Some(fixed_now() + Duration::days(365)),
1139        ));
1140
1141        assert!(is_device_listed(
1142            &root_did,
1143            &device_did,
1144            &[att_expired, att_revoked, att_valid],
1145            fixed_now()
1146        ));
1147    }
1148
1149    #[test]
1150    fn is_device_listed_returns_false_for_wrong_identity() {
1151        let (_, root_pk) = create_test_keypair(&[1u8; 32]);
1152        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1153        let (other_kp, other_pk) = create_test_keypair(&[3u8; 32]);
1154        let other_did = auths_crypto::ed25519_pubkey_to_did_key(&other_pk);
1155        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1156        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1157        let device_did = DeviceDID::new(&device_did_str);
1158
1159        let att = create_signed_attestation(
1160            &other_kp,
1161            &device_kp,
1162            &other_did,
1163            &device_did_str,
1164            None,
1165            Some(fixed_now() + Duration::days(365)),
1166        );
1167        assert!(!is_device_listed(
1168            &root_did,
1169            &device_did,
1170            &[verified(att)],
1171            fixed_now()
1172        ));
1173    }
1174
1175    #[test]
1176    fn is_device_listed_returns_false_for_wrong_device() {
1177        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1178        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1179        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1180        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1181        let (_, other_device_pk) = create_test_keypair(&[4u8; 32]);
1182        let other_device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&other_device_pk);
1183        let other_device_did = DeviceDID::new(&other_device_did_str);
1184
1185        let att = create_signed_attestation(
1186            &root_kp,
1187            &device_kp,
1188            &root_did,
1189            &device_did_str,
1190            None,
1191            Some(fixed_now() + Duration::days(365)),
1192        );
1193        assert!(!is_device_listed(
1194            &root_did,
1195            &other_device_did,
1196            &[verified(att)],
1197            fixed_now()
1198        ));
1199    }
1200
1201    /// Helper to create a signed attestation with org fields
1202    fn create_signed_attestation_with_org_fields(
1203        issuer_kp: &Ed25519KeyPair,
1204        device_kp: &Ed25519KeyPair,
1205        issuer_did: &str,
1206        subject_did: &str,
1207        role: Option<Role>,
1208        capabilities: Vec<Capability>,
1209    ) -> Attestation {
1210        let device_pk: [u8; 32] = device_kp.public_key().as_ref().try_into().unwrap();
1211
1212        let mut att = Attestation {
1213            version: 1,
1214            rid: ResourceId::new("test-rid"),
1215            issuer: IdentityDID::new(issuer_did),
1216            subject: DeviceDID::new(subject_did),
1217            device_public_key: Ed25519PublicKey::from_bytes(device_pk),
1218            identity_signature: Ed25519Signature::empty(),
1219            device_signature: Ed25519Signature::empty(),
1220            revoked_at: None,
1221            expires_at: Some(fixed_now() + Duration::days(365)),
1222            timestamp: Some(fixed_now()),
1223            note: None,
1224            payload: None,
1225            role,
1226            capabilities: capabilities.clone(),
1227            delegated_by: None,
1228            signer_type: None,
1229        };
1230
1231        let caps_ref = if att.capabilities.is_empty() {
1232            None
1233        } else {
1234            Some(&att.capabilities)
1235        };
1236        let data = CanonicalAttestationData {
1237            version: att.version,
1238            rid: &att.rid,
1239            issuer: &att.issuer,
1240            subject: &att.subject,
1241            device_public_key: att.device_public_key.as_bytes(),
1242            payload: &att.payload,
1243            timestamp: &att.timestamp,
1244            expires_at: &att.expires_at,
1245            revoked_at: &att.revoked_at,
1246            note: &att.note,
1247            role: att.role.as_ref().map(|r| r.as_str()),
1248            capabilities: caps_ref,
1249            delegated_by: att.delegated_by.as_ref(),
1250            signer_type: att.signer_type.as_ref(),
1251        };
1252        let canonical_bytes = canonicalize_attestation_data(&data).unwrap();
1253
1254        att.identity_signature =
1255            Ed25519Signature::try_from_slice(issuer_kp.sign(&canonical_bytes).as_ref()).unwrap();
1256        att.device_signature =
1257            Ed25519Signature::try_from_slice(device_kp.sign(&canonical_bytes).as_ref()).unwrap();
1258
1259        att
1260    }
1261
1262    fn create_signed_attestation_with_caps(
1263        issuer_kp: &Ed25519KeyPair,
1264        device_kp: &Ed25519KeyPair,
1265        issuer_did: &str,
1266        subject_did: &str,
1267        capabilities: Vec<Capability>,
1268    ) -> Attestation {
1269        create_signed_attestation_with_org_fields(
1270            issuer_kp,
1271            device_kp,
1272            issuer_did,
1273            subject_did,
1274            None,
1275            capabilities,
1276        )
1277    }
1278
1279    #[tokio::test]
1280    async fn verify_with_capability_succeeds_when_capability_present() {
1281        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1282        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1283        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1284        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1285
1286        let att = create_signed_attestation_with_caps(
1287            &root_kp,
1288            &device_kp,
1289            &root_did,
1290            &device_did,
1291            vec![Capability::sign_commit(), Capability::sign_release()],
1292        );
1293
1294        let result = test_verifier()
1295            .verify_with_capability(&att, &Capability::sign_commit(), &root_pk)
1296            .await;
1297        assert!(result.is_ok());
1298    }
1299
1300    #[tokio::test]
1301    async fn verify_with_capability_fails_when_capability_missing() {
1302        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1303        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1304        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1305        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1306
1307        let att = create_signed_attestation_with_caps(
1308            &root_kp,
1309            &device_kp,
1310            &root_did,
1311            &device_did,
1312            vec![Capability::sign_commit()],
1313        );
1314
1315        let result = test_verifier()
1316            .verify_with_capability(&att, &Capability::manage_members(), &root_pk)
1317            .await;
1318        assert!(result.is_err());
1319        match result {
1320            Err(AttestationError::MissingCapability {
1321                required,
1322                available,
1323            }) => {
1324                assert_eq!(required, Capability::manage_members());
1325                assert_eq!(available, vec![Capability::sign_commit()]);
1326            }
1327            _ => panic!("Expected MissingCapability error"),
1328        }
1329    }
1330
1331    #[tokio::test]
1332    async fn verify_with_capability_fails_for_invalid_signature() {
1333        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1334        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1335        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1336        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1337
1338        let mut att = create_signed_attestation_with_caps(
1339            &root_kp,
1340            &device_kp,
1341            &root_did,
1342            &device_did,
1343            vec![Capability::sign_commit()],
1344        );
1345        let mut tampered = *att.identity_signature.as_bytes();
1346        tampered[0] ^= 0xFF;
1347        att.identity_signature = Ed25519Signature::from_bytes(tampered);
1348
1349        let result = test_verifier()
1350            .verify_with_capability(&att, &Capability::sign_commit(), &root_pk)
1351            .await;
1352        assert!(result.is_err());
1353        match result {
1354            Err(AttestationError::IssuerSignatureFailed(_)) => {}
1355            _ => panic!("Expected IssuerSignatureFailed, got {:?}", result),
1356        }
1357    }
1358
1359    #[tokio::test]
1360    async fn verify_chain_with_capability_succeeds_for_single_link() {
1361        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1362        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1363        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1364        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1365
1366        let att = create_signed_attestation_with_caps(
1367            &root_kp,
1368            &device_kp,
1369            &root_did,
1370            &device_did,
1371            vec![Capability::sign_commit(), Capability::sign_release()],
1372        );
1373
1374        let result = test_verifier()
1375            .verify_chain_with_capability(&[att], &Capability::sign_commit(), &root_pk)
1376            .await;
1377        assert!(result.is_ok());
1378        assert!(result.unwrap().is_valid());
1379    }
1380
1381    #[tokio::test]
1382    async fn verify_chain_with_capability_uses_intersection() {
1383        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1384        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1385        let (identity_kp, identity_pk) = create_test_keypair(&[2u8; 32]);
1386        let identity_did = auths_crypto::ed25519_pubkey_to_did_key(&identity_pk);
1387        let (device_kp, device_pk) = create_test_keypair(&[3u8; 32]);
1388        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1389
1390        let att1 = create_signed_attestation_with_org_fields(
1391            &root_kp,
1392            &identity_kp,
1393            &root_did,
1394            &identity_did,
1395            None,
1396            vec![Capability::sign_commit(), Capability::manage_members()],
1397        );
1398        let att2 = create_signed_attestation_with_org_fields(
1399            &identity_kp,
1400            &device_kp,
1401            &identity_did,
1402            &device_did,
1403            None,
1404            vec![Capability::sign_commit(), Capability::sign_release()],
1405        );
1406
1407        let result = test_verifier()
1408            .verify_chain_with_capability(
1409                &[att1.clone(), att2.clone()],
1410                &Capability::sign_commit(),
1411                &root_pk,
1412            )
1413            .await;
1414        assert!(result.is_ok());
1415
1416        let result = test_verifier()
1417            .verify_chain_with_capability(&[att1, att2], &Capability::manage_members(), &root_pk)
1418            .await;
1419        assert!(result.is_err());
1420        match result {
1421            Err(AttestationError::MissingCapability { required, .. }) => {
1422                assert_eq!(required, Capability::manage_members());
1423            }
1424            _ => panic!("Expected MissingCapability error"),
1425        }
1426    }
1427
1428    #[tokio::test]
1429    async fn verify_chain_with_capability_returns_report_on_invalid_chain() {
1430        let result = test_verifier()
1431            .verify_chain_with_capability(&[], &Capability::sign_commit(), &[0u8; 32])
1432            .await;
1433        assert!(result.is_ok());
1434        let report = result.unwrap();
1435        assert!(!report.is_valid());
1436    }
1437
1438    #[tokio::test]
1439    async fn verify_attestation_rejects_tampered_role() {
1440        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1441        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1442        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1443        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1444
1445        let mut att = create_signed_attestation_with_org_fields(
1446            &root_kp,
1447            &device_kp,
1448            &root_did,
1449            &device_did,
1450            Some(Role::Member),
1451            vec![Capability::sign_commit()],
1452        );
1453
1454        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1455        assert!(result.is_ok(), "Attestation should verify before tampering");
1456
1457        att.role = Some(Role::Admin);
1458        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1459        assert!(result.is_err(), "Attestation should reject tampered role");
1460        let err_msg = result.unwrap_err().to_string();
1461        assert!(
1462            err_msg.contains("signature"),
1463            "Error should mention signature failure: {}",
1464            err_msg
1465        );
1466    }
1467
1468    #[tokio::test]
1469    async fn verify_attestation_rejects_tampered_capabilities() {
1470        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1471        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1472        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1473        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1474
1475        let mut att = create_signed_attestation_with_org_fields(
1476            &root_kp,
1477            &device_kp,
1478            &root_did,
1479            &device_did,
1480            Some(Role::Member),
1481            vec![Capability::sign_commit()],
1482        );
1483        assert!(
1484            test_verifier()
1485                .verify_with_keys(&att, &root_pk)
1486                .await
1487                .is_ok()
1488        );
1489
1490        att.capabilities.push(Capability::manage_members());
1491        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1492        assert!(
1493            result.is_err(),
1494            "Attestation should reject tampered capabilities"
1495        );
1496    }
1497
1498    #[tokio::test]
1499    async fn verify_attestation_rejects_tampered_delegated_by() {
1500        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1501        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1502        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1503        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1504
1505        let mut att = create_signed_attestation_with_org_fields(
1506            &root_kp,
1507            &device_kp,
1508            &root_did,
1509            &device_did,
1510            Some(Role::Member),
1511            vec![Capability::sign_commit()],
1512        );
1513        assert!(
1514            test_verifier()
1515                .verify_with_keys(&att, &root_pk)
1516                .await
1517                .is_ok()
1518        );
1519
1520        att.delegated_by = Some(IdentityDID::new("did:key:attacker"));
1521        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1522        assert!(
1523            result.is_err(),
1524            "Attestation should reject tampered delegated_by"
1525        );
1526    }
1527
1528    #[tokio::test]
1529    async fn verify_attestation_valid_with_org_fields() {
1530        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1531        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1532        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1533        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1534
1535        let att = create_signed_attestation_with_org_fields(
1536            &root_kp,
1537            &device_kp,
1538            &root_did,
1539            &device_did,
1540            Some(Role::Admin),
1541            vec![Capability::sign_commit(), Capability::manage_members()],
1542        );
1543
1544        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1545        assert!(
1546            result.is_ok(),
1547            "Attestation with org fields should verify: {:?}",
1548            result.err()
1549        );
1550    }
1551
1552    fn create_signed_attestation_with_timestamp(
1553        issuer_kp: &Ed25519KeyPair,
1554        device_kp: &Ed25519KeyPair,
1555        issuer_did: &str,
1556        subject_did: &str,
1557        timestamp: Option<DateTime<Utc>>,
1558    ) -> Attestation {
1559        let device_pk: [u8; 32] = device_kp.public_key().as_ref().try_into().unwrap();
1560
1561        let mut att = Attestation {
1562            version: 1,
1563            rid: ResourceId::new("test-rid"),
1564            issuer: IdentityDID::new(issuer_did),
1565            subject: DeviceDID::new(subject_did),
1566            device_public_key: Ed25519PublicKey::from_bytes(device_pk),
1567            identity_signature: Ed25519Signature::empty(),
1568            device_signature: Ed25519Signature::empty(),
1569            revoked_at: None,
1570            expires_at: Some(fixed_now() + Duration::days(365)),
1571            timestamp,
1572            note: None,
1573            payload: None,
1574            role: None,
1575            capabilities: vec![],
1576            delegated_by: None,
1577            signer_type: None,
1578        };
1579
1580        let data = CanonicalAttestationData {
1581            version: att.version,
1582            rid: &att.rid,
1583            issuer: &att.issuer,
1584            subject: &att.subject,
1585            device_public_key: att.device_public_key.as_bytes(),
1586            payload: &att.payload,
1587            timestamp: &att.timestamp,
1588            expires_at: &att.expires_at,
1589            revoked_at: &att.revoked_at,
1590            note: &att.note,
1591            role: att.role.as_ref().map(|r| r.as_str()),
1592            capabilities: if att.capabilities.is_empty() {
1593                None
1594            } else {
1595                Some(&att.capabilities)
1596            },
1597            delegated_by: att.delegated_by.as_ref(),
1598            signer_type: att.signer_type.as_ref(),
1599        };
1600        let canonical_bytes = canonicalize_attestation_data(&data).unwrap();
1601
1602        att.identity_signature =
1603            Ed25519Signature::try_from_slice(issuer_kp.sign(&canonical_bytes).as_ref()).unwrap();
1604        att.device_signature =
1605            Ed25519Signature::try_from_slice(device_kp.sign(&canonical_bytes).as_ref()).unwrap();
1606
1607        att
1608    }
1609
1610    #[tokio::test]
1611    async fn verify_attestation_created_1_hour_ago_succeeds() {
1612        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1613        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1614        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1615        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1616
1617        let one_hour_ago = fixed_now() - Duration::hours(1);
1618        let att = create_signed_attestation_with_timestamp(
1619            &root_kp,
1620            &device_kp,
1621            &root_did,
1622            &device_did,
1623            Some(one_hour_ago),
1624        );
1625
1626        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1627        assert!(
1628            result.is_ok(),
1629            "Attestation created 1 hour ago should verify: {:?}",
1630            result.err()
1631        );
1632    }
1633
1634    #[tokio::test]
1635    async fn verify_attestation_created_30_days_ago_succeeds() {
1636        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1637        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1638        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1639        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1640
1641        let thirty_days_ago = fixed_now() - Duration::days(30);
1642        let att = create_signed_attestation_with_timestamp(
1643            &root_kp,
1644            &device_kp,
1645            &root_did,
1646            &device_did,
1647            Some(thirty_days_ago),
1648        );
1649
1650        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1651        assert!(
1652            result.is_ok(),
1653            "Attestation created 30 days ago should verify: {:?}",
1654            result.err()
1655        );
1656    }
1657
1658    #[tokio::test]
1659    async fn verify_attestation_with_future_timestamp_fails() {
1660        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1661        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1662        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1663        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1664
1665        let ten_minutes_future = fixed_now() + Duration::minutes(10);
1666        let att = create_signed_attestation_with_timestamp(
1667            &root_kp,
1668            &device_kp,
1669            &root_did,
1670            &device_did,
1671            Some(ten_minutes_future),
1672        );
1673
1674        let result = test_verifier().verify_with_keys(&att, &root_pk).await;
1675        assert!(
1676            result.is_err(),
1677            "Attestation with future timestamp should fail"
1678        );
1679        let err_msg = result.unwrap_err().to_string();
1680        assert!(
1681            err_msg.contains("in the future"),
1682            "Error should mention 'in the future': {}",
1683            err_msg
1684        );
1685    }
1686
1687    #[tokio::test]
1688    async fn verify_device_authorization_returns_valid_for_signed_attestation() {
1689        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1690        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1691        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1692        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1693        let device_did = DeviceDID::new(&device_did_str);
1694
1695        let att = create_signed_attestation(
1696            &root_kp,
1697            &device_kp,
1698            &root_did,
1699            &device_did_str,
1700            None,
1701            Some(fixed_now() + Duration::days(365)),
1702        );
1703
1704        let result = test_verifier()
1705            .verify_device_authorization(&root_did, &device_did, &[att], &root_pk)
1706            .await;
1707        assert!(result.is_ok());
1708        let report = result.unwrap();
1709        assert!(report.is_valid());
1710        assert_eq!(report.chain.len(), 1);
1711        assert!(report.chain[0].valid);
1712    }
1713
1714    #[tokio::test]
1715    async fn verify_device_authorization_returns_broken_chain_for_no_attestations() {
1716        let (_, root_pk) = create_test_keypair(&[1u8; 32]);
1717        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1718        let (_, device_pk) = create_test_keypair(&[2u8; 32]);
1719        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1720        let device_did = DeviceDID::new(&device_did_str);
1721
1722        let result = test_verifier()
1723            .verify_device_authorization(&root_did, &device_did, &[], &root_pk)
1724            .await;
1725        assert!(result.is_ok());
1726        let report = result.unwrap();
1727        assert!(!report.is_valid());
1728        match report.status {
1729            VerificationStatus::BrokenChain { missing_link } => {
1730                assert!(missing_link.contains("No attestation found"));
1731            }
1732            _ => panic!("Expected BrokenChain status, got {:?}", report.status),
1733        }
1734    }
1735
1736    #[tokio::test]
1737    async fn verify_device_authorization_fails_for_forged_signature() {
1738        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1739        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1740        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1741        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1742        let device_did = DeviceDID::new(&device_did_str);
1743
1744        let mut att = create_signed_attestation(
1745            &root_kp,
1746            &device_kp,
1747            &root_did,
1748            &device_did_str,
1749            None,
1750            Some(fixed_now() + Duration::days(365)),
1751        );
1752        let mut tampered = *att.identity_signature.as_bytes();
1753        tampered[0] ^= 0xFF;
1754        att.identity_signature = Ed25519Signature::from_bytes(tampered);
1755
1756        let result = test_verifier()
1757            .verify_device_authorization(&root_did, &device_did, &[att], &root_pk)
1758            .await;
1759        assert!(result.is_ok());
1760        let report = result.unwrap();
1761        assert!(!report.is_valid());
1762        match report.status {
1763            VerificationStatus::InvalidSignature { step } => assert_eq!(step, 0),
1764            _ => panic!("Expected InvalidSignature status, got {:?}", report.status),
1765        }
1766    }
1767
1768    #[tokio::test]
1769    async fn verify_device_authorization_fails_for_wrong_issuer_key() {
1770        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1771        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1772        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1773        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1774        let device_did = DeviceDID::new(&device_did_str);
1775        let (_, wrong_pk) = create_test_keypair(&[99u8; 32]);
1776
1777        let att = create_signed_attestation(
1778            &root_kp,
1779            &device_kp,
1780            &root_did,
1781            &device_did_str,
1782            None,
1783            Some(fixed_now() + Duration::days(365)),
1784        );
1785
1786        let result = test_verifier()
1787            .verify_device_authorization(&root_did, &device_did, &[att], &wrong_pk)
1788            .await;
1789        assert!(result.is_ok());
1790        let report = result.unwrap();
1791        assert!(!report.is_valid());
1792        match report.status {
1793            VerificationStatus::InvalidSignature { .. } => {}
1794            _ => panic!("Expected InvalidSignature status, got {:?}", report.status),
1795        }
1796    }
1797
1798    #[tokio::test]
1799    async fn verify_device_authorization_checks_expiry_and_revocation() {
1800        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1801        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1802        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1803        let device_did_str = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1804        let device_did = DeviceDID::new(&device_did_str);
1805
1806        let att_expired = create_signed_attestation(
1807            &root_kp,
1808            &device_kp,
1809            &root_did,
1810            &device_did_str,
1811            None,
1812            Some(fixed_now() - Duration::days(1)),
1813        );
1814        let result = test_verifier()
1815            .verify_device_authorization(&root_did, &device_did, &[att_expired], &root_pk)
1816            .await;
1817        assert!(result.is_ok());
1818        let report = result.unwrap();
1819        assert!(!report.is_valid());
1820        match report.status {
1821            VerificationStatus::Expired { .. } => {}
1822            _ => panic!("Expected Expired status, got {:?}", report.status),
1823        }
1824
1825        let att_revoked = create_signed_attestation(
1826            &root_kp,
1827            &device_kp,
1828            &root_did,
1829            &device_did_str,
1830            Some(fixed_now()),
1831            Some(fixed_now() + Duration::days(365)),
1832        );
1833        let result = test_verifier()
1834            .verify_device_authorization(&root_did, &device_did, &[att_revoked], &root_pk)
1835            .await;
1836        assert!(result.is_ok());
1837        let report = result.unwrap();
1838        assert!(!report.is_valid());
1839        match report.status {
1840            VerificationStatus::Revoked { .. } => {}
1841            _ => panic!("Expected Revoked status, got {:?}", report.status),
1842        }
1843    }
1844
1845    fn create_witness_receipt(
1846        witness_kp: &Ed25519KeyPair,
1847        witness_did: &str,
1848        event_said: &str,
1849        seq: u64,
1850    ) -> crate::witness::WitnessReceipt {
1851        let mut receipt = crate::witness::WitnessReceipt {
1852            v: "KERI10JSON000000_".into(),
1853            t: "rct".into(),
1854            d: Said::new_unchecked(format!("EReceipt_{}", seq)),
1855            i: witness_did.into(),
1856            s: seq,
1857            a: Said::new_unchecked(event_said.to_string()),
1858            sig: vec![],
1859        };
1860        let payload = receipt.signing_payload().unwrap();
1861        receipt.sig = witness_kp.sign(&payload).as_ref().to_vec();
1862        receipt
1863    }
1864
1865    #[tokio::test]
1866    async fn verify_chain_with_witnesses_valid() {
1867        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1868        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1869        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1870        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1871
1872        let att = create_signed_attestation(
1873            &root_kp,
1874            &device_kp,
1875            &root_did,
1876            &device_did,
1877            None,
1878            Some(fixed_now() + Duration::days(365)),
1879        );
1880
1881        let (w1_kp, w1_pk) = create_test_keypair(&[10u8; 32]);
1882        let (w2_kp, w2_pk) = create_test_keypair(&[20u8; 32]);
1883
1884        let r1 = create_witness_receipt(&w1_kp, "did:key:w1", "EEvent1", 1);
1885        let r2 = create_witness_receipt(&w2_kp, "did:key:w2", "EEvent1", 1);
1886
1887        let witness_keys = vec![
1888            ("did:key:w1".into(), w1_pk.to_vec()),
1889            ("did:key:w2".into(), w2_pk.to_vec()),
1890        ];
1891
1892        let config = crate::witness::WitnessVerifyConfig {
1893            receipts: &[r1, r2],
1894            witness_keys: &witness_keys,
1895            threshold: 2,
1896        };
1897
1898        let report = test_verifier()
1899            .verify_chain_with_witnesses(&[att], &root_pk, &config)
1900            .await
1901            .unwrap();
1902        assert!(report.is_valid());
1903        assert!(report.witness_quorum.is_some());
1904        let quorum = report.witness_quorum.unwrap();
1905        assert_eq!(quorum.required, 2);
1906        assert_eq!(quorum.verified, 2);
1907    }
1908
1909    #[tokio::test]
1910    async fn verify_chain_with_witnesses_chain_fails() {
1911        let (w1_kp, w1_pk) = create_test_keypair(&[10u8; 32]);
1912        let r1 = create_witness_receipt(&w1_kp, "did:key:w1", "EEvent1", 1);
1913        let witness_keys = vec![("did:key:w1".into(), w1_pk.to_vec())];
1914        let config = crate::witness::WitnessVerifyConfig {
1915            receipts: &[r1],
1916            witness_keys: &witness_keys,
1917            threshold: 1,
1918        };
1919
1920        let report = test_verifier()
1921            .verify_chain_with_witnesses(&[], &[0u8; 32], &config)
1922            .await
1923            .unwrap();
1924        assert!(!report.is_valid());
1925        match report.status {
1926            VerificationStatus::BrokenChain { .. } => {}
1927            _ => panic!("Expected BrokenChain, got {:?}", report.status),
1928        }
1929        assert!(report.witness_quorum.is_none());
1930    }
1931
1932    #[tokio::test]
1933    async fn verify_chain_with_witnesses_quorum_fails() {
1934        let (root_kp, root_pk) = create_test_keypair(&[1u8; 32]);
1935        let root_did = auths_crypto::ed25519_pubkey_to_did_key(&root_pk);
1936        let (device_kp, device_pk) = create_test_keypair(&[2u8; 32]);
1937        let device_did = auths_crypto::ed25519_pubkey_to_did_key(&device_pk);
1938
1939        let att = create_signed_attestation(
1940            &root_kp,
1941            &device_kp,
1942            &root_did,
1943            &device_did,
1944            None,
1945            Some(fixed_now() + Duration::days(365)),
1946        );
1947
1948        let (w1_kp, w1_pk) = create_test_keypair(&[10u8; 32]);
1949        let r1 = create_witness_receipt(&w1_kp, "did:key:w1", "EEvent1", 1);
1950        let witness_keys = vec![("did:key:w1".into(), w1_pk.to_vec())];
1951        let config = crate::witness::WitnessVerifyConfig {
1952            receipts: &[r1],
1953            witness_keys: &witness_keys,
1954            threshold: 2,
1955        };
1956
1957        let report = test_verifier()
1958            .verify_chain_with_witnesses(&[att], &root_pk, &config)
1959            .await
1960            .unwrap();
1961        assert!(!report.is_valid());
1962        match report.status {
1963            VerificationStatus::InsufficientWitnesses { required, verified } => {
1964                assert_eq!(required, 2);
1965                assert_eq!(verified, 1);
1966            }
1967            _ => panic!("Expected InsufficientWitnesses, got {:?}", report.status),
1968        }
1969        assert!(report.witness_quorum.is_some());
1970        assert!(!report.warnings.is_empty());
1971    }
1972}